Linux设备驱动中的并发控制(四)

前言

之前咱们不是自己写了个驱动globalmem的设备驱动嘛,不过当时只有简单的驱动文件描述符。这里我们不是学习了并发,于是这里给咱们的这个驱动增加上。

增加并发控制后的globalmem的设备驱动

在globalmem()的读写函数中,由于要调用copy_from_user()、copy_to_user()这些可能导致阻塞的函数,因此不能使用自旋锁,宜使用互斥体。(阻塞的就不适合用自旋锁)

驱动工程师习惯将某设备所使用的自旋锁、互斥体等辅助手段也放在设备结构中,因此,可如代码清单7.4那样修改globalmem_dev结构体的定义,并在模块初始化函数中初始化这个信号量,如代码清单7.5所示。

增加并发控制后的globalmem设备结构体

 1struct globalmem_dev {
 2      struct cdev cdev;
 3      unsigned char mem[GLOBALMEM_SIZE];
 4      struct mutex mutex;
 5};

增加并发控制后的globalmem设备驱动模块加载函数

 1static int __init globalmem_init(void)
 2 {
 3  int ret;
 4  dev_t devno = MKDEV(globalmem_major, 0);
 5
 6  if (globalmem_major)
 7       ret = register_chrdev_region(devno, 1, "globalmem");
 8  else {
 9       ret = alloc_chrdev_region(&devno, 0, 1, "globalmem");
10       globalmem_major = MAJOR(devno);
11  }
12  if (ret < 0)
13       return ret;
14
15  globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
16  if (!globalmem_devp) {
17       ret = -ENOMEM;
18       goto fail_malloc;
19  }
20
21  mutex_init(&globalmem_devp->mutex);
22  globalmem_setup_cdev(globalmem_devp, 0);
23  return 0;
24
25 fail_malloc:
26  unregister_chrdev_region(devno, 1);
27  return ret;
28 }
29module_init(globalmem_init);

在访问globalmem_dev中的共享资源时,需先获取这个互斥体,访问完成后,随即释放这个互斥体。驱动中新的globalmem读、写操作如代码清单7.6所示。

增加并发控制后的globalmem读、写操作

 1static ssize_t globalmem_read(struct file *filp, char __user * buf, size_t size,
 2              loff_t * ppos)
 3{
 4 unsigned long p = *ppos;
 5 unsigned int count = size;
 6 int ret = 0;
 7 struct globalmem_dev *dev = filp->private_data;
 8
 9 if (p >= GLOBALMEM_SIZE)
10      return 0;
11 if (count > GLOBALMEM_SIZE - p)
12      count = GLOBALMEM_SIZE - p;
13
14 mutex_lock(&dev->mutex);
15
16 if (copy_to_user(buf, dev->mem + p, count)) {
17      ret = -EFAULT;
18 } else {
19      *ppos += count;
20      ret = count;
21
22      printk(KERN_INFO "read %u bytes(s) from %lu\n", count, p);
23 }
24
25 mutex_unlock(&dev->mutex);
26
27 return ret;
28 }
29
30static ssize_t globalmem_write(struct file *filp, const char __user * buf,
31                size_t size, loff_t * ppos)
32 {
33 unsigned long p = *ppos;
34 unsigned int count = size;
35 int ret = 0;
36 struct globalmem_dev *dev = filp->private_data;
37
38 if (p >= GLOBALMEM_SIZE)
39      return 0;
40 if (count > GLOBALMEM_SIZE - p)
41      count = GLOBALMEM_SIZE - p;
42
43 mutex_lock(&dev->mutex);
44
45 if (copy_from_user(dev->mem + p, buf, count))
46      ret = -EFAULT;
47 else {
48      *ppos += count;
49      ret = count;
50
51      printk(KERN_INFO "written %u bytes(s) from %lu\n", count, p);
52 }
53
54 mutex_unlock(&dev->mutex);
55
56 return ret;
57 }

代码第14行和第43行用于获取互斥体,代码第25和54行用于在对临界资源访问结束后释放信号量。除了globalmem的读、写操作之外,如果在读、写的同时,另一个执行单元执行MEM_CLEAR IO控制命令,也会导致全局内存的混乱,因此,globalmem_ioctl()函数也需被重写,如代码清单7.7所示。

增加并发控制后的globalmem设备驱动ioctl()函数

 1static long globalmem_ioctl(struct file *filp, unsigned int cmd,
 2             unsigned long arg)
 3{
 4 struct globalmem_dev *dev = filp->private_data;   /* μ éè±  á11ì      */
 5
 6 switch (cmd) {
 7 case MEM_CLEAR:
 8     mutex_lock(&dev->mutex);
 9     memset(dev->mem, 0, GLOBALMEM_SIZE);
10     mutex_unlock(&dev->mutex);
11
12     printk(KERN_INFO "globalmem is set to zero\n");
13     break;
14
15 default:
16     return -EINVAL;
17 }
18
19 return 0;
20}

增加并发控制后globalmem的完整驱动位于本书虚拟机的例子/kernel/drivers/globalmem/ch7目录下,其使用方法与第6章globalmem驱动在用户空间的验证一致。

总结

并发和竞态广泛存在,中断屏蔽、原子操作、自旋锁和互斥体都是解决并发问题的机制。中断屏蔽很少单独被使用,原子操作只能针对整数进行,因此自旋锁和互斥体应用最为广泛。

自旋锁会导致死循环,锁定期间不允许阻塞因此要求锁定的临界区小。互斥体允许临界区阻塞,可以适用于临界区大的情况。

内容来自:Linux设备驱动开发详解 感谢这些前辈的总结

Guess you like

Origin blog.csdn.net/weixin_45264425/article/details/130793968