Kernel / VM Advent Calendar 2013 Jour 3: Essayons la fonction Lock appelée lockref du noyau Linux

Ce journal est l'article du troisième jour du Kernel / VM Advent Calendar 2013. Cette fois, j'aimerais jeter un œil au mécanisme de verrouillage appelé lockref, qui a été introduit dans Linux 3.12. Cette fonction est introduite dans l'article Présentation des références de verrouillage de LWN.

Selon cet article, «Dans de nombreux cas, le compteur de référence est défini comme de type atomic_t et vous pouvez utiliser la variable sans prendre le verrou, mais dans le cas de atomic_t, le compteur de référence peut être mis à jour indépendamment des autres structures de données de la structure. Cela fonctionne dans certains cas. Sinon, vous devez verrouiller toute la structure (super-traduction). " Et, dans l'article, en utilisant la structure dentée comme exemple, il est expliqué que le verrou tournant sera un goulot d'étranglement, et la solution à cela est lockref.

Donc, si vous regardez include / linux / lockref.h, il y a une définition de la structure lockref.

 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 };

Je n'ai jamais entendu parler du type align_u64 φ (..) Vous pouvez l'utiliser comme compteur de référence. L'interface lors de l'utilisation de cette structure de données avait les 6 fonctions suivantes.

 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 *);

En regardant lib / lockref.c, les commentaires ont été écrits pour chaque fonction, il est donc normal de les utiliser correctement en regardant les commentaires. pense.

Utilisons ce code pendant un moment. C'est un code de test qui incrémente la variable de comptage avec 3 modèles de lockref, spinlock et atomic_t, qui sont décidés au moment de la compilation.

#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);

Le contenu du test est que le temps (nanoseconde) requis pour exécuter locktest_run_test () est mesuré en incrémentant le compteur dans la variable globale struct locktest_data testdata en bouclant 1000000 plus bas dans chaque processeur. Définissez et exécutez environ 100 fois avec le script suivant.

#!/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

L'environnement que j'ai essayé était x86_64, Arch Linux, et le noyau était 3.13-rc2. Le processeur est i7 3770S (le nombre de processeurs visibles depuis le noyau est de 8), et le résultat est ↓. result.png

Eh bien, dans le cas du lockref, il y a beaucoup de variation de vitesse. spinlock est un temps stable. atomic_inc () est encore plus rapide car il ne prend pas de verrou. Je me demande si ce cas de test n'est pas le cas d'utilisation attendu par lockref.

Recommended Posts

Kernel / VM Advent Calendar 2013 Jour 3: Essayons la fonction Lock appelée lockref du noyau Linux
Essayez le mécanisme de verrouillage du noyau Linux
Essayons Linux pour la première fois