前言
之前咱们不是自己写了个驱动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设备驱动开发详解 感谢这些前辈的总结