Kernel / VM Adventskalender 2013 Tag 3: Probieren wir die Lock-Funktion lockref des Linux-Kernels aus

Dieses Tagebuch ist der dritte Tagesartikel des Kernel / VM-Adventskalenders 2013. Dieses Mal möchte ich einen Blick auf den Sperrmechanismus namens lockref werfen, der in Linux 3.12 eingeführt wurde. Diese Funktion wird im Artikel Einführung in Sperrreferenzen von LWN eingeführt.

In diesem Artikel heißt es: "In vielen Fällen ist der Referenzzähler als atomic_t-Typ definiert, und Sie können die Variable ohne Aufheben der Sperre bedienen. In atomic_t kann der Referenzzähler jedoch unabhängig von anderen Datenstrukturen der Struktur aktualisiert werden. In einigen Fällen funktioniert es. Wenn nicht, müssen Sie die gesamte Struktur sperren (Superübersetzung). " In dem Artikel wird am Beispiel der Dentry-Struktur erklärt, dass Spinlock ein Engpass sein wird, und die Lösung dafür ist Lockref.

Wenn Sie sich also include / linux / lockref.h ansehen, gibt es eine Definition der lockref-Struktur.

 19 struct lockref {
 20         union {
 21 #ifdef CONFIG_CMPXCHG_LOCKREF
 22                 aligned_u64 lock_count;
 23 #endif
 24                 struct {
 25                         spinlock_t lock;
 26                         unsigned int count;
 27                 };
 28         };
 29 };

Ich habe noch nie von align_u64 Typ φ (..) gehört. Sie können dies als Referenzzähler verwenden. Die Schnittstelle bei Verwendung dieser Datenstruktur hatte die folgenden 6 Funktionen.

 31 extern void lockref_get(struct lockref *);
 32 extern int lockref_get_not_zero(struct lockref *);
 33 extern int lockref_get_or_lock(struct lockref *);
 34 extern int lockref_put_or_lock(struct lockref *);
 35 
 36 extern void lockref_mark_dead(struct lockref *);
 37 extern int lockref_get_not_dead(struct lockref *);

In lib / lockref.c wurden Kommentare für jede Funktion geschrieben, daher ist es in Ordnung, sie ordnungsgemäß zu verwenden, indem Sie sich die Kommentare ansehen. Überlegen.

Lassen Sie uns diesen Code für einen Moment verwenden. Es ist ein Testcode, der die Zählvariable mit 3 Mustern von lockref, spinlock und atomic_t erhöht, die zur Kompilierungszeit festgelegt werden.

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/smp.h>
#include <linux/time.h>
#include <linux/debugfs.h>
#include <linux/string.h>

MODULE_DESCRIPTION("lock test module");
MODULE_AUTHOR("masami256");
MODULE_LICENSE("GPL");

struct testfunc_ops {
	void(*inc_count)(void);
	unsigned int (*get_count)(void);
};

struct locktest_data;

struct locktest_data testdata;

static s64 total_nanosec;

#define LOCKTEST_USE_LOCKREF 
//#define LOCKTEST_USE_ATOMIC_INC

#ifdef LOCKTEST_USE_LOCKREF
#define LOCKTEST_LOCK_NAME "lockref"
#include <linux/lockref.h>
struct locktest_data {
	struct lockref ld_lockref;
};

static void 
locktest_lockref_inc_count(void)
{
	lockref_get(&testdata.ld_lockref);
}

static unsigned int
locktest_lockref_get_count(void)
{
	return testdata.ld_lockref.count;
}

struct testfunc_ops testfunc = {
	.inc_count = locktest_lockref_inc_count,
	.get_count = locktest_lockref_get_count,
};

#elif defined(LOCKTEST_USE_ATOMIC_INC)
#include <linux/spinlock.h>
#define LOCKTEST_LOCK_NAME "atomic_t"
struct locktest_data {
	atomic_t count;
};

static void 
locktest_atomic_inc_count(void)
{
	atomic_inc(&testdata.count);
}

static unsigned int
locktest_atomic_get_count(void)
{
	return (unsigned int) atomic_read(&testdata.count);
}

struct testfunc_ops testfunc = {
	.inc_count = locktest_atomic_inc_count,
	.get_count = locktest_atomic_get_count,
};

#else // use spinlock
#define LOCKTEST_LOCK_NAME "spinlock"
#include <linux/spinlock.h>
struct locktest_data {
	unsigned int count;
	spinlock_t ld_lock;
};

static void
locktest_spinlock_inc_count(void)
{
	spin_lock(&testdata.ld_lock);
	testdata.count++;
	spin_unlock(&testdata.ld_lock);
}

static unsigned int
locktest_spinlock_get_count(void)
{
	return testdata.count;
}

struct testfunc_ops testfunc = {
	.inc_count = locktest_spinlock_inc_count,
	.get_count = locktest_spinlock_get_count,
};

#endif // LOCKTEST_USE_LOCKREF

#define LOOP_COUNT 1000000
static unsigned int expected_count;

static void
locktest_run_test(void *info)
{
	int i = 0;
	for (i = 0; i < LOOP_COUNT; i++)
		testfunc.inc_count();
}

static s64
locktest_get_current_time_as_ns(void)
{
	struct timeval v;
	do_gettimeofday(&v);

	return timeval_to_ns(&v);
}

static void
save_test_total_time(s64 start, s64 end)
{
	total_nanosec = end - start;
}

static int num_cpus;

static void
locktest_start_test(void)
{
	s64 start, end;

	start = locktest_get_current_time_as_ns();
	on_each_cpu(&locktest_run_test, NULL, 1);
	end = locktest_get_current_time_as_ns();

	save_test_total_time(start, end);
	printk(KERN_INFO "Test took %lld nanoseconds\n", total_nanosec);

	WARN_ON(unlikely(expected_count != testfunc.get_count()));
}

// debugfs operations.
static struct dentry *locktest_dir;
static struct dentry *locktest_tc;
static struct dentry *locktest_result;
static char locktest_data[8];
static char locktest_result_data[64];

static ssize_t
locktest_read_result(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
	memset(locktest_result_data, 0, sizeof(locktest_result_data));
	sprintf(locktest_result_data, "%lld", total_nanosec);

	return simple_read_from_buffer(buf, len, ppos, locktest_result_data, sizeof(locktest_result_data));
}

static ssize_t
locktest_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
	return simple_read_from_buffer(buf, len, ppos, locktest_data, sizeof(locktest_data));
}

static ssize_t
locktest_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
	ssize_t s = simple_write_to_buffer(locktest_data, sizeof(locktest_data), ppos, buf, len);

	if (!strncmp(locktest_data, "run", 3))
		locktest_start_test();
	else if(!strncmp(locktest_data, "reset", 5))
		memset(&testdata, 0, sizeof(testdata));

	memset(locktest_data, 0x0, sizeof(locktest_data));
	return s;
}

static int
locktest_check_debugfs_func_result(const struct dentry *entry)
{
	if (!entry)
		return -1;

	if (ERR_PTR(-ENODEV) == entry)
		return -ENODEV;

	return 0;
}

static void
locktest_remove_debugfs_dir(void)
{
	if (locktest_dir)
		debugfs_remove_recursive(locktest_dir);
}

static int 
locktest_create_debugfs_directory(void)
{
	locktest_dir = debugfs_create_dir("locktest", NULL);
	return locktest_check_debugfs_func_result(locktest_dir);
}

struct file_operations locktest_fops = {
	.owner = THIS_MODULE,
	.read = locktest_read,
	.write = locktest_write,
};

struct file_operations locktest_result_fops = {
	.owner = THIS_MODULE,
	.read = locktest_read_result,
};

static int
locktest_create_file(void)
{
	int ret = 0;

	locktest_tc= debugfs_create_file("testcase", 0644, locktest_dir, &locktest_data, &locktest_fops);
	ret = locktest_check_debugfs_func_result(locktest_tc);
	if (ret)
		return ret;

	locktest_result = debugfs_create_file("test_result", 0644, locktest_dir, &locktest_result, &locktest_result_fops);
	ret = locktest_check_debugfs_func_result(locktest_tc);
	if (ret)
		return ret;

	return 0;
}

static int
locktest_init(void)
{
	int ret = 0;
	ret = locktest_create_debugfs_directory();
	if (ret)
		goto error_out;

	ret = locktest_create_file();
	if (ret)
		goto error_out;

	num_cpus = num_online_cpus();
	expected_count = num_cpus * LOOP_COUNT;

	printk(KERN_INFO "Use %s mechanism\n", LOCKTEST_LOCK_NAME);
	printk(KERN_INFO "cpus: %d\n", num_cpus);
	printk(KERN_INFO "module loaded\n");
	return 0;

error_out:
	locktest_remove_debugfs_dir();
	return ret;
}

static void
locktest_cleanup(void)
{
	locktest_remove_debugfs_dir();
	printk(KERN_INFO "module unloaded\n");
}

module_init(locktest_init);
module_exit(locktest_cleanup);

Der Inhalt des Tests besteht darin, dass die Zeit (Nanosekunde), die zum Ausführen von locktest_run_test () erforderlich ist, durch Inkrementieren des Zählers in der globalen Variablen struct locktest_data testdata gemessen wird, während in jeder CPU 1000000 weniger Schleifen ausgeführt werden. Mit dem folgenden Skript etwa 100 Mal festlegen und ausführen.

#!/bin/bash

modfile=/home/masami/codes/locktest/locktest.ko
if [ ! -f $modfile ]; then
    echo $modfile is not found.
    exit -1
fi

insmod $modfile

debugfs_path=/sys/kernel/debug/locktest
for i in {0..100}
do
    echo run > $debugfs_path/testcase
    result=`cat $debugfs_path/test_result`
    echo $i,$result
    echo reset > $debugfs_path/testcase
done

rmmod locktest

exit 0

Die Umgebung, die ich ausprobiert habe, war x86_64, Arch Linux, und der Kernel war 3.13-rc2. Die CPU ist i7 3770S (die Anzahl der CPUs, die vom Kernel aus gesehen werden können, beträgt 8), und das Ergebnis ist ↓. result.png

Nun, im Fall von Lockref gibt es viele Unterschiede in der Geschwindigkeit. Spinlock ist eine stabile Zeit. atomic_inc () ist immer noch schneller, da es keine Sperre benötigt. Ich frage mich, ob dieser Testfall nicht der Anwendungsfall ist, den lockref erwartet.

Recommended Posts

Kernel / VM Adventskalender 2013 Tag 3: Probieren wir die Lock-Funktion lockref des Linux-Kernels aus
Probieren Sie den Linux-Kernel-Sperrmechanismus aus
Versuchen wir zum ersten Mal Linux