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 ↓.
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.