驱动篇:堵塞实战
发布日期:2021-06-29 11:34:44 浏览次数:2 分类:技术文章

本文共 4571 字,大约阅读时间需要 15 分钟。

驱动篇:堵塞实战

把 globalfifo 中的全局内存变成一个 FIFO,只有当 FIFO 中有数据的时候(即有进程把数据写到这个 FIFO 而且没有被读进程读空), 读进程才能把数据读出, 而且读取后的数据会从 globalfifo 的全局内存中被拿掉;只有当 FIFO 非满时 (即还有一些空间未被写, 或写满后被读进程从这个 FIFO 中读出了数据),写进程才能往这个 FIFO 中写入数据。

globalmem_dev重命名为globalfifo_dev并在其中增加两个等待队列头,分别对应于读和写

struct globalfifo_dev {
struct cdev cdev; /*cdev 结构体*/ unsigned int current_len; /*表征目前FIFO 中有效数据的长度*/ unsigned char mem[GLOBALFIFO_SIZE]; /*全局内存*/ struct semaphore sem; /*并发控制用的信号量*/ wait_queue_head_t r_wait; /*阻塞读用的等待队列头*/ wait_queue_head_t w_wait; /*阻塞写用的等待队列头*//*fifo 有效数据长度*/ };

等待队列需在设备驱动模块加载函数中调用 init_waitqueue_head()初始化

增加等待队列后的 globalfifo 初始化函数

/*设备驱动模块加载函数*/ int globalfifo_init(void) {
int result; dev_t devno = MKDEV(globalmem_major, 0); /* 申请设备号*/ if (globalfifo_major)result = register_chrdev_region(devno, 2, "globalfifo_devp"); else /* 动态申请设备号 */ {
result = alloc_chrdev_region(&devno, 0, 2, "globalfifo_devp");globalmem_major = MAJOR(devno); } if (result < 0)return result; /* 动态申请两个设备结构体的内存*/globalfifo_devp = kmalloc(2*sizeof(struct globalfifo_devp),GFP_KERNEL); if (!globalfifo_devp)/*申请失败*/{
result = - ENOMEM;goto fail_malloc; } memset(globalfifo_devp, 0, 2*sizeof(struct globalfifo_devp)); globalfifo_setup_cdev(globalfifo_devp[0], 0); globalfifo_setup_cdev(&globalfifo_devp[1], 1); /*初始化信号量*/ init_MUTEX(&globalfifo_devp->sem); init_waitqueue_head(&globalfifo_devp->r_wait); /*初始化读等待队列头*/ init_waitqueue_head(&globalfifo_devp->w_wait); /*初始化写等待队列头*/ return 0; fail_malloc: unregister_chrdev_region(devno, 1); return result; }

增加等待队列后的 globalfifo 读函数

static ssize_t globalfifo_read(struct file *filp, char _ _user *buf,size_t count,loff_t *ppos) {
int ret = 0;struct globalfifo_dev *dev = filp->private_data; /*获得设备结构体指针*/DECLARE_WAITQUEUE(wait, current); //定义等待队列down(&dev->sem); //获得信号量add_wait_queue(&dev->r_wait, &wait); //进入读等待队列头/* 等待 FIFO 非空 */ if (dev->current_len == 0){
if (filp->f_flags &O_NONBLOCK) {
ret = - EAGAIN;goto out; } _ _set_current_state(TASK_INTERRUPTIBLE); //改变进程状态为睡眠 up(&dev->sem); schedule(); //调度其他进程执行 if (signal_pending(current)) //如果是因为信号唤醒 {
ret = - ERESTARTSYS; goto out2;}down(&dev->sem);} /* 复制到用户空间 */ if (count > dev->current_len)count = dev->current_len; if (copy_to_user(buf, dev->mem, count)) {
ret = - EFAULT; goto out; } else {
memcpy(dev->mem, dev->mem + count, dev->current_len - count);//fifo 数据前移dev->current_len -= count; //有效数据长度减少printk(KERN_INFO "read %d bytes(s),current_len:%d\n", count,dev->current_len);wake_up_interruptible(&dev->w_wait); //唤醒写等待队列ret = count;}out: up(&dev->sem); //释放信号量out2: remove_wait_queue(&dev->w_wait, &wait); //从附属的等待队列头移除set_current_state(TASK_RUNNING);return ret;}

增加等待队列后的 globalfifo 写函数

static ssize_t globalfifo_write(struct file *filp, const char __user *buf,size_t count, loff_t *ppos) {
int ret = 0; struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/DECLARE_WAITQUEUE(wait, current); //定义等待队列down(&dev->sem); //获取信号量add_wait_queue(&dev->w_wait, &wait); //进入写等待队列头/* 等待 FIFO 非满 */if (dev->current_len == GLOBALFIFO_SIZE){
if (filp->f_flags &O_NONBLOCK)//如果是非阻塞访问{
ret = - EAGAIN;goto out;}__set_current_state(TASK_INTERRUPTIBLE); //改变进程状态为睡眠up(&dev->sem);schedule(); //调度其他进程执行if (signal_pending(current))//如果是因为信号唤醒{
ret = - ERESTARTSYS;goto out2;}down(&dev->sem); //获得信号量}/*从用户空间复制到内核空间*/if (count > GLOBALFIFO_SIZE - dev->current_len)count = GLOBALFIFO_SIZE - dev->current_len;if (copy_from_user(dev->mem + dev->current_len, buf, count)){
ret = - EFAULT;goto out;}else{
dev->current_len += count;printk(KERN_INFO "written %d bytes(s),current_len:%d\n",->current_len);wake_up_interruptible(&dev->r_wait); //唤醒读等待队列ret = count;}out: up(&dev->sem); //释放信号量out2: remove_wait_queue(&dev->w_wait, &wait); //从附属的等待队列set_current_state(TASK_RUNNING);return ret;}

驱动工程师一定要注意:当多个等待队列、信号量等机制同时出现时,谨防死锁。所谓死锁,就是多个进程循环等待它方占有的资源而无限期地僵持下去的局面。如果没有外力的作用,那么死锁涉及的各个进程都将永远处于封锁状态。设备驱动的 read()、write()等功能函数中,可以通过 filp->f_flags 标志获得用户空间是否要求非阻塞访问。驱动中可以依据此标志判断用户究竟要求阻塞还是非阻塞访问,从而进行不同的处理。

在这里插入图片描述上图(a),假设目前的 FIFO 为空,即 dev->current_len 为 0,此时如果有 一 个 读 进程, 它 会 先 获得 信号量 , 因 为条件 不 满足 , 它将 因 为wait_event_interruptible(dev->r_wait, dev-> current_len != 0) 而 阻 塞 , 而 释 放dev->r_wait 等待队列及让 dev->current_len != 0 的操作又需要在写进程中进行,写进程在执行写操作前又必须等待读进程释放信号量,造成互相等待对方资源的矛盾局面,从而死锁。

上图(b),假设目前的 FIFO 为满即 dev->current_len 为 GLOBALFIFO_SIZE,此时如 果 有 一 个写进程, 它 先 获得 信号量 , 因 为条件 不 满足 , 它将 因为wait_event_interruptible ( dev->w_wait, dev->current_len != GLOBALFIFO_SIZE) 而阻塞,而释放 dev->w_wait 等待队列及让 dev->current_ len != GLOBALFIFO_SIZE 的操作又需要在读进程中进行, 读进程在执行读操作前又必须等待写进程释放信号量, 造成互相等待对方资源的矛盾局面,从而死锁。

转载地址:https://blog.csdn.net/zytgg123456/article/details/109893446 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:驱动篇:轮询操作(一)
下一篇:驱动篇:阻塞与非阻塞 I/O(摘录)

发表评论

最新留言

路过按个爪印,很不错,赞一个!
[***.219.124.196]2024年04月10日 04时06分17秒