同步管理
实验:互斥锁
linux 内核中的 mutex 互斥锁是一种同步机制,和我们之前了解的应用层中的同步概念是完全一样。相比于自旋锁,互斥锁对 CPU 的占用更少,其在占用状态会进入睡眠状态,直到锁被释放。
我们来了解一下互斥锁这套 API:初始化使用 mutex_init 函数;加锁使用 mutex_lock 函数;尝试加锁使用 mutex_trylock 函数;解锁使用 mutex_unlock 函数。
在之前的虚拟 FIFO 设备驱动中,我们没有考虑到多进程访问设备的情况,现在使用互斥锁对需要的资源进行保护。
- #include <linux/module.h>
- #include <linux/fs.h>
- #include <linux/uaccess.h>
- #include <linux/init.h>
- #include <linux/miscdevice.h>
- #include <linux/device.h>
- #include <linux/slab.h>
- #include <linux/kfifo.h>
- #include <linux/wait.h>
- #include <linux/sched.h>
- #include <linux/cdev.h>
- #include <linux/poll.h>
- #define DEMO_NAME "mydemo_dev"
- #define MYDEMO_FIFO_SIZE 64
- static dev_t dev;
- static struct cdev* demo_cdev;
- static struct class* mydemo_class;
- struct mydemo_device
- {
- char name[64];
- struct device* dev;
- wait_queue_head_t read_queue;
- wait_queue_head_t write_queue;
- struct kfifo mydemo_fifo;
- struct fasync_struct* fasync;
- struct mutex lock;
- };
- struct mydemo_private_data
- {
- struct mydemo_device* device;
- char name[64];
- };
- #define MYDEMO_MAX_DEVICES 8
- static struct mydemo_device* mydemo_device[MYDEMO_MAX_DEVICES];
- static int demodrv_open(struct inode* inode, struct file* file)
- {
- unsigned int minor = iminor(inode);
- struct mydemo_private_data* data;
- struct mydemo_device* device = mydemo_device[minor];
- dev_info(device->dev, "%s: major=%d, minor=%d, device=%s\n", __func__,
- MAJOR(inode->i_rdev), MINOR(inode->i_rdev), device->name);
- data = kmalloc(sizeof(struct mydemo_private_data), GFP_KERNEL);
- if (!data)
- return -ENOMEM;
- sprintf(data->name, "private_data_%d", minor);
- data->device = device;
- file->private_data = data;
- }
- static int demodrv_release(struct inode* inode, struct file* file)
- {
- struct mydemo_private_data* data = file->private_data;
- kfree(data);
- return 0;
- }
- static ssize_t demodrv_read(struct file* file, char __user* buf, size_t count, loff_t* ppos)
- {
- struct mydemo_private_data* data = file->private_data;
- struct mydemo_device* device = data->device;
- int actual_readed;
- int ret;
- if (kfifo_is_empty(&device->mydemo_fifo))
- {
- if (file->f_flags & O_NONBLOCK)
- return -EAGAIN;
- dev_info(device->dev, "%s:%s pid=%d, going to sleep, %s\n", __func__,
- device->name, current->pid, data->name);
- ret = wait_event_interruptible(device->read_queue, !kfifo_is_empty(&device->mydemo_fifo));
- if (ret)
- return ret;
- }
- mutex_lock(&device->lock);
- ret = kfifo_to_user(&device->mydemo_fifo, buf, count, &actual_readed);
- if (ret)
- return -EIO;
- mutex_unlock(&device->lock);
- if (!kfifo_is_full(&device->mydemo_fifo))
- {
- wake_up_interruptible(&device->write_queue);
- kill_fasync(&device->fasync, SIGIO, POLL_OUT);
- }
- dev_info(device->dev, "%s:%s, pid=%d, actual_readed=%d, pos=%lld\n", __func__,
- device->name, current->pid, actual_readed, *ppos);
- return actual_readed;
- }
- static ssize_t demodrv_write(struct file* file, const char __user* buf, size_t count, loff_t* ppos)
- {
- struct mydemo_private_data* data = file->private_data;
- struct mydemo_device* device = data->device;
- unsigned int actual_write;
- int ret;
- if (kfifo_is_full(&device->mydemo_fifo))
- {
- if (file->f_flags & O_NONBLOCK)
- return -EAGAIN;
- dev_info(device->dev, "%s:%s pid=%d, going to sleep\n", __func__,
- device->name, current->pid);
- ret = wait_event_interruptible(device->write_queue, !kfifo_is_full(&device->mydemo_fifo));
- if (ret)
- return ret;
- }
- mutex_lock(&device->lock);
- ret = kfifo_from_user(&device->mydemo_fifo, buf, count, &actual_write);
- if (ret)
- return -EIO;
- mutex_unlock(&device->lock);
- if (!kfifo_is_empty(&device->mydemo_fifo))
- {
- wake_up_interruptible(&device->read_queue);
- kill_fasync(&device->fasync, SIGIO, POLL_IN);
- printk("%s kill fasync\n", __func__);
- }
- dev_info(device->dev, "%s:%s pid=%d, actual_write=%d, ppos=%lld, ret=%d\n", __func__,
- device->name, current->pid, actual_write, *ppos, ret);
- return actual_write;
- }
- static unsigned int demodrv_poll(struct file* file, poll_table* wait)
- {
- int mask = 0;
- struct mydemo_private_data* data = file->private_data;
- struct mydemo_device* device = data->device;
- mutex_lock(&device->lock);
- poll_wait(file, &device->read_queue, wait);
- poll_wait(file, &device->write_queue, wait);
- if (!kfifo_is_empty(&device->mydemo_fifo))
- mask |= POLLIN | POLLRDNORM;
- if (!kfifo_is_full(&device->mydemo_fifo))
- maks |= POLLOUT | POLLWRNORM;
- mutex_unlock(&device->lock);
- return mask;
- }
- static int demodrv_fasync(int fd, struct file* file, int on)
- {
- struct mydemo_private_data* data = file->private_data;
- struct mydemo_device* device = data->device;
- int ret;
- mutex_lock(&device->lock);
- dev_info(device->dev, "%s send SIGIO\n", __func__);
- ret = fasync_helper(fd, file, on, &device->fasync);
- mutex_unlock(&device->lock);
- return ret;
- }
- static const struct file_operations demodrv_fops =
- {
- .owner = THIS_MODULE,
- .open = demodrv_open,
- .release = demodrv_release,
- .read = demodrv_read,
- .write = demodrv_write,
- .poll = demodrv_poll,
- .fasync = demodrv_fasync,
- };
- static int __init simple_char_init()
- {
- int ret;
- int i;
- struct mydemo_device* device;
- ret = alloc_chrdev_region(&dev, 0, MYDEMO_MAX_DEVICES, DEMO_NAME);
- if (ret)
- {
- printk("failed to allocate char device region\n");
- return ret;
- }
- demo_cdev = cdev_alloc();
- if (!demo_cdev)
- {
- printk("cdev_alloc failed\n");
- goto unregister_chrdev;
- }
- cdev_init(demo_cdev, &demodrv_fops);
- ret = cdev_add(demo_cdev, dev, MYDEMO_MAX_DEVICES);
- if (ret)
- {
- printk("cdev_add failed\n");
- goto cdev_fail;
- }
- mydemo_class = class_create(THIS_MODULE, "my_class");
- for (i = 0; i < MYDEMO_MAX_DEVICES; i++)
- {
- device = kzalloc(sizeof(struct mydemo_device), GFP_KERNEL);
- if (!device)
- {
- ret = -ENOMEM;
- goto free_device;
- }
- sprintf(device->name, "%s%d", DEMO_NAME, i);
- mutex_init(&device->lock);
- device->dev = device_create(mydemo_class, NULL, MKDEV(dev, i), NULL, "mydemo:%d:%d", MAJOR(dev), i);
- dev_info(device->dev, "create device: %d:%d\n", MAJOR(dev), MINOR(i));
- mydemo_device[i] = device;
- init_waitqueue_head(&device->read_queue);
- init_waitqueue_head(&device->write_queue);
- ret = kfifo_alloc(&device->mydemo_fifo, MYDEMO_FIFO_SIZE, GFP_KERNEL);
- if (ret)
- {
- ret = -ENOMEM;
- goto free_kfifo;
- }
- printk("mydemo_fifo=%p\n", &device->mydemo_fifo);
- }
- printk("succeeded register char device: %s\n", DEMO_NAME);
- return 0;
- free_kfifo:
- for (i = 0; i < MYDEMO_MAX_DEVICES; i++)
- if (&device->mydemo_fifo)
- kfifo_free(&device->mydemo_fifo);
- free_device:
- for (i = 0; i < MYDEMO_MAX_DEVICES; i++)
- if (mydemo_device[i])
- kfree(mydemo_device[i]);
- cdev_fail:
- cdev_del(demo_cdev);
- unregister_chrdev:
- unregister_chrdev_region(dev, MYDEMO_MAX_DEVICES);
- return ret;
- }
- static void __exit simple_char_exit()
- {
- int i;
- printk("removing device\n");
- if (demo_cdev)
- cdev_del(demo_cdev);
- unregister_chrdev_region(dev, MYDEMO_MAX_DEVICES);
- for (i = 0; i < MYDEMO_MAX_DEVICES; i++)
- {
- if (mydemo_device[i])
- {
- device_destroy(mydemo_class, MKDEV(dev, i));
- kfree(mydemo_device[i]);
- }
- }
- class_destroy(mydemo_class);
- }
- module_init(simple_char_init);
- module_exit(simple_char_exit);
- MODULE_AUTHOR("rlk");
- MODULE_LICENSE("GPL v2");
- MODULE_DESCRIPTION("simpe character device");
代码清单 1 有点长,我们主要特意关注使用互斥锁同步的地方。demodrv_read 和 demodrv_write 中,我们对 FIFO 的读写操作进行同步,确保只有一个进程使用;demodrv_poll 中,我们对读写等待队列和 FIFO 状态读取进行同步;demodrv_fasync 中,我们对异步通知列表进行同步。
fasync 机制回过头来看有点陌生,重新温习一下。
.fasync 接口维护异步通知列表;kill_fasync 进行通知。
实验:RCU 锁
参照上一个互斥锁的实验,如果现在是读取密集型的场景。读写之间是必须要同步的,但是多个读线程也受互斥锁同步控制,效率很低。
针对读取密集场景,linux 内核使用 RCU 锁提高性能。我们看一下 RCU 的基本原理:
Read: 多个读取者可以同时访问数据,允许并发无锁的读取数据。
Copy: 需要修改数据时,先创建一个数据的副本,在副本上先做修改。
Update: 当确定没有线程读取旧数据时,将副本作为新数据进行更新。
以上也就是 RCU 的缩写。Read 和 Copy 很好理解,主要是不明白 Update 中是如何确定没有线程读取旧数据的。此处我们先不做深究,只知道使用相关的标准 API 能达到这个目的就可以了。
MARK: 如何确定没有线程读取旧数据?
我们来了解 RCU 锁的相关 API:
rcu_read_lock:进入 RCU 读取临界区。
rcu_read_unlock:离开 RCU 读取临界区。
rcu_dereference:安全的读取 RCU 受保护的指针。
rcu_assign_pointer:安全的发布新的 RCU 受保护的指针。
synchronize_rcu:阻塞直到所有正在进行的 RCU 读取临界区结束。
call_rcu:synchronize_rcu 的异步版本,满足条件时执行指定的回调函数。
最后我们看 RCU 锁的示例代码。如代码清单 2 演示了使用 RCU 机制来同步 struct foo 结构体的访问,创建了读者线程和写者线程。
读者线程通过 rcu_read_lock、rcu_read_unlock 和 rcu_dereference 安全的读取共享数据。
写者线程使用 rcu_assign_pointer 发布新数据,同时使用 call_rcu 在适当时候释放旧数据。
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/slab.h>
- #include <linux/spinlock.h>
- #include <linux/rcupdate.h>
- #include <linux/kthread.h>
- #include <linux/delay.h>
- struct foo
- {
- int a;
- struct rcu_head rcu;
- };
- static struct foo* g_ptr;
- // 读者线程 1
- static int myrcu_reader_thread1(void* data)
- {
- struct foo* p1 = NULL;
- while (1)
- {
- if (kthread_should_stop())
- break;
- msleep(20);
- rcu_read_lock();
- mdelay(200);
- p1 = rcu_dereference(g_ptr);
- if (p1)
- printk("%s: read a=%d\n", __func__, p1->a);
- rcu_read_unlock();
- }
- return 0;
- }
- // 读者线程 2
- static int myrcu_reader_thread2(void* data)
- {
- struct foo* p2 = NULL;
- while (1)
- {
- if (kthread_should_stop())
- break;
- msleep(30);
- rcu_read_lock();
- mdelay(100);
- p2 = rcu_dereference(g_ptr);
- if (p2)
- printk("%s: read a=%d\n", __func__, p2->a);
- rcu_read_unlock();
- }
- return 0;
- }
- static void myrcu_del(struct rcu_head* rh)
- {
- struct foo* p = container_of(rh, struct foo, rcu);
- printk("%s: a=%d\n", __func__, p->a);
- kfree(p);
- }
- // 写者线程
- static int myrcu_writer_thread(void* p)
- {
- struct foo* old;
- struct foo* new_ptr;
- int value = (unsigned long)p;
- while (1)
- {
- if (kthread_should_stop())
- break;
- msleep(250);
- new_ptr = kmalloc(sizeof(struct foo), GFP_KERNEL);
- old = g_ptr;
- *new_ptr = *old;
- new_ptr->a = value;
- rcu_assign_pointer(g_ptr, new_ptr);
- call_rcu(&old->rcu, myrcu_del);
- printk("%s: write to new %d\n", __func__, value);
- value++;
- }
- return 0;
- }
- static struct task_struct* reader_thread1;
- static struct task_struct* reader_thread2;
- static struct task_struct* writer_thread;
- static int __init my_test_init(void)
- {
- int value = 5;
- printk("figo: my module init\n");
- g_ptr = kzalloc(sizeof(struct foo), GFP_KERNEL);
- reader_thread1 = kthread_run(myrcu_reader_thread1, NULL, "rcu_reader1");
- reader_thread2 = kthread_run(myrcu_reader_thread2, NULL, "rcu_reader2");
- writer_thread = kthread_run(myrcu_writer_thread, (void*)(unsigned long)value, "rcu_write");
- return 0;
- }
- static void __exit my_test_exit(void)
- {
- printk("goodbye\n");
- kthread_stop(reader_thread1);
- kthread_stop(reader_thread2);
- kthread_stop(writer_thread);
- if (g_ptr)
- kfree(g_ptr);
- }
- MODULE_LICENSE("GPL");
- module_init(my_test_init);
- module_exit(my_test_exit);