驱动篇:并发实战
在 globalmem()的读写函数中,由于要调用 copy_from_user()、copy_to_user()这些可能导致阻塞的函数,因此不能使用自旋锁,宜使用信号量。驱动工程师习惯将某设备所使用的自旋锁、信号量等辅助手段也放在设备结构中并在模块初始化函数中初始化这个信号量
struct globalmem_dev
{
struct cdev cdev; /*cdev 结构体*/
unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
struct semaphore sem; /*并发控制用的信号量*/
};
设备驱动模块加载函数
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
int result;
dev_t devno = MKDEV(globalmem_major, 0);
/* 申请设备号*/
if (globalmem_major)
result = register_chrdev_region(devno, 2, "globalmem");
else /* 动态申请设备号 */
{
result = alloc_chrdev_region(&devno, 0, 2, "globalmem");
globalmem_major = MAJOR(devno);
}
if (result < 0)
return result;
/* 动态申请两个设备结构体的内存*/
globalmem_devp = kmalloc(2*sizeof(struct globalmem_dev),
GFP_KERNEL);
if (!globalmem_devp)
/*申请失败*/
{
result = - ENOMEM;
goto fail_malloc;
}
memset(globalmem_devp, 0, 2*sizeof(struct globalmem_dev));
globalmem_setup_cdev(globalmem_devp[0], 0);
globalmem_setup_cdev(&globalmem_devp[1], 1);
/*初始化信号量*/
init_MUTEX(&globalfifo_devp->sem);
return 0;
fail_malloc: unregister_chrdev_region(devno, 1);
return result;
}
在访问 globalmem_dev 中的共享资源时,需先获取这个信号量,访问完成后,随即释放这个信号量。
增加并发控制后的 globalmem 读写操作
static ssize_t globalmem_read(struct file *filp, char _ _user *buf,size_t count,loff_t *ppos)
{
unsigned long p = *ppos;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的读长度*/
if (p >= GLOBALMEM_SIZE) //要读的偏移位置越界
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p)//要读的字节数太大
count = GLOBALMEM_SIZE - p;
if (down_interruptible(&dev->sem)) //获得信号量
{
return - ERESTARTSYS;
}
/*内核空间→用户空间*/
if (copy_to_user(buf, (void*)(dev.mem + p), count))
{
ret = - EFAULT;
}
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
}
up(&dev->sem); //释放信号量
return ret;
}
增加并发控制后的 globalmem 写函数
static ssize_t globalmem_write(struct file *filp, const char _ _user *buf,size_t count, loff_t *ppos)
{
unsigned long p = *ppos;
int ret = 0;
struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/
/*分析和获取有效的写长度*/
if (p >= GLOBALMEM_SIZE)
//要写的偏移位置越界
return count ? - ENXIO: 0;
if (count > GLOBALMEM_SIZE - p) //要写的字节数太多
count = GLOBALMEM_SIZE - p;
if (down_interruptible(&dev->sem)) //获得信号量
{
return - ERESTARTSYS;
}
/*用户空间→内核空间*/
if (copy_from_user(dev->mem + p, buf, count))
ret = - EFAULT;
else
{
*ppos += count;
ret = count;
printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
}
up(&dev->sem); //释放信号量
return ret;
}
down_interruptible()返回值非 0,则意味着其在获得信号量之前已被打断,这时写函数返回-ERESTARTSYS。
除了 globalmem 的读写操作之外,如果在读写的同时,另一执行单元执行MEM_CLEAR IO 控制命令,也会导致全局内存的混乱,因此,globalmem_ioctl()函数也需被重写
static int globalmem_ioctl(struct inode *inodep, struct file *filp,
unsigned int cmd, unsigned long arg)
{
switch (cmd)
{
case MEM_CLEAR:
if (down_interruptible(&dev->sem))//获得信号量
{
return - ERESTARTSYS;
}
//清除全局内存
memset(dev->mem, 0, GLOBALMEM_SIZE);
up(&dev->sem); //释放信号量
printk(KERN_INFO "globalmem is set to zero\n");
break;
default:
return - EINVAL; //其他不支持的命令
}
return 0;
}
并发和竞态广泛存在,中断屏蔽、原子操作、自旋锁和信号量都是解决并发问题的机制。中断屏蔽很少单独被使用,原子操作只能针对整数进行,因此自旋锁和信号量应用最为广泛。自旋锁会导致死循环,锁定期间不允许阻塞,因此要求锁定的临界区小。信号量允许临界区阻塞,可以适用于临界区大的情况。
读写自旋锁和读写信号量分别是放宽了条件的自旋锁和信号量,它们允许多个执行单元对共享资源的并发读。