休眠(被阻塞)的进程处于一个特殊的不可执行状态(TASK_INTERRUPBTILBE、TASK_UNINTERRUPTIBLE)。内核操作过程:进程把它自己标记成休眠状态,把自己从可执行队列移出并放入等待队列,然后调用schedule()选择和执行一个其他进程。唤醒的过程刚好相反:进程被设置为可执行状态,然后再从等待队列中移到可执行队列。
休眠通过等待队列进行处理。等待队列是有某些事件发生的进程组成的简单链表。进程把自己放入等待队列中并设置成不可执行状态。当与等待队列相关的事件发生的时候,队列上的进程会被唤醒。为了避免产生竞争条件,休眠和唤醒的实现不能有纰漏。
- DECLEARE_WAITQUEUE(wait,current);
-
- add_wait_queue(q,&wait);//q是我们希望睡眠的等待队列
- while(!conditon)
- {
- set_current_state(TASK_INTERRUPTIBLE);// or TASK_UNINTERRRUPTIBLE
- if(signal_pending(currrent))
- /*处理信号*/
- schedule();
- }
- set_current_state(TASK_RUNNING);
- remove_wait_queue(q,&wait);
1.调用DECLARE_WAITQUEEU创建一个等待队列的项
2.调用add_wait_queue()把自己加入到队列中。该队列会在进程等待的条件满足时唤醒它。当然我们必须在其他地方撰写相关代码,在事件发生时,对等待队列进行wake_up()操作。
3.将进程的状态更为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE
4.如果状态呗置为TASK_INTERRUPTIBLE,则信号唤醒进程。这就是所谓的 伪唤醒(唤醒不是因为时间的发生),因此检查并处理信号。
5.检查条件是否为真;如果是的话,就没必要休眠了。如果条件不为真,调用schedule();
6.当进程被唤醒的时候,它会再次检查条件是否为真。如果是,它就退出循环,如果不是,它再次调用schedule()并一直重复这步操作。
7.当条件满足后,进程将自己设置为TASK_RUNNING并调用remove_wait_queue把自己移除等待队列。设置为TASK_RUNNING,并不是这个进程马上被执行,而是在运行队列中,根据优先级等进程调度规则,进程调度执行。
等待队列操作:
1.定义“等待队列头”
- wait_queue_head_t my_queue;
2.初始化“等待队列头”
- init_waitqueue_head(&my_queue);
快捷方式:
- DECLARE_WAIT_QUEUE_HEAD(name)
3.定义等待队列
- DECLARE_WAITQUEUE(name,tsk);
4.添加/移除等待队列
- void faskcall add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
- void fastcall remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait);
5.等待事件
- wait_event(queue,condidtion);
- wait_event_interruptible(queue,conditon);
- wait_event_timeout(queue,conditon,timeout);
- wait_event_interruptible_timeout(queu,conditon,timeout);
6.唤醒队列
以下操作会唤醒以queue等待队列头的所有等待队列中所有属于该等待队列头的等待队列对应的进程。
- void wake_up(wait_queue_head_t *queue);
- void wake_up_interruptible(wait_queue_head_t *queue);
程序在“2 并发控制semaphore rwlock”上修改如下:
添加头文件
- #include <linux/wait.h>// waitqueuee
- #include <linux/sched.h>//schedule()
结构体修改
- struct globalmem_dev{
- ...
- unsigned int current_len; //设备数据缓冲区中,当前数据大小
- wait_queue_head_t r_wait; // read
- wait_queue_head_t w_wait; // write
- };
read系统调用驱动
- static ssize_t globalmem_read(struct file *filp,char __user *buf,size_t size,loff_t *ppos)
- {
- // unsigned long p = *ppos;
- unsigned int count = size;
- int ret = 0;
- struct globalmem_dev *dev = filp->private_data;
-
- DECLARE_WAITQUEUE(wait,current);///////////waitqueue_t定义等待队列
-
- if(down_interruptible(&dev->sem)) //获得锁,如果没能获得,返回-ERESTARTSYS,系统重新调用这个调用
- return -ERESTARTSYS;
-
- add_wait_queue(&dev->r_wait,&wait);//add to wait queue
-
- while(dev->current_len == 0) //假如缓冲中, 没有可读数据, 空数据
- {
- //如果是非阻塞模式,没有数据可读,马上返回-EAGAIN,提示在调用一次,也行就成功了
- if(filp->f_flags & O_NONBLOCK) //O_NONBLOCK 非阻塞
- { //在应用程序open中,默认是阻塞打开文件的。但是可设置O_NOBLOCK
- ret = -EAGAIN; //非阻塞,这需要驱动程序中提供相关功能
- goto out; //假如是非阻塞,马上返回 返回EAGAIN提示其再调用一次(也许下次就能成功)
- }
-
- //如果是阻塞模式
- __set_current_state(TASK_INTERRUPTIBLE);//改变进程为睡眠模式
- up(&dev->sem);//释放当前进程的信号量,进入 等待队列
-
- schedule();//using other process显式调用schedule()进行进程切换,执行其他进程
- //到这里,本进程就停止在这里了,不向下执行了
- if(signal_pending(current))//假如是由于信号产生的 伪信号 唤醒进程,这里就出错,并返回
- {
- ret = -ERESTARTSYS;
- goto out2;
- }
- //本进程再次运行时,获得信号量,保护 临界区 资源
- if(down_interruptible(&dev->sem))
- return -ERESTARTSYS;
- }
//上述中为什么要用while,而不用if进行判断?????
//特殊情况下,在可中断的情况下被唤醒,而数据空间还是为空,则会出错,所以要用while继续进行判断数据空间是否有数据
-
- 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("read %d byte(s),current_len:%d\n",count,dev->current_len);
- wake_up_interruptible(&dev->w_wait);////唤醒写等待队列
- ret = count;
-
- }
- ssleep(1);/////////////////////////应用程序中,测试时间用
- out:
- up(&dev->sem);
- out2:
- remove_wait_queue(&dev->w_wait,&wait);/////////////
- set_current_state(TASK_RUNNING);//////////////
- return ret;
- }
write系统调用驱动
- static ssize_t globalmem_write(struct file *filp,const char __user *buf,size_t size,loff_t*ppos)
- {
- // unsigned long p = *ppos;
- unsigned int count = size;
- int ret = 0;
- struct globalmem_dev *dev = filp->private_data;
- DECLARE_WAITQUEUE(wait,current);/////////////////
-
- if(down_interruptible(&dev->sem))
- return -ERESTARTSYS;
- add_wait_queue(&dev->w_wait,&wait);/////////////////
-
- while(dev->current_len == GLOBALMEM_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;
- }
-
- if(down_interruptible(&dev->sem))
- return -ERESTARTSYS;
-
- }
- if(count > GLOBALMEM_SIZE - dev->current_len)
- {
- count = GLOBALMEM_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("writting %d bytes,current_len:%d\n",count,dev->current_len);
- wake_up_interruptible(&dev->r_wait);
- ret = count;
- }
- ssleep(1); /////////////////////应用程序中,测试时间用
- out: up(&dev->sem);
- out2: remove_wait_queue(&dev->w_wait,&wait);
- return ret;
- }
在init函数中添加
- // globalmem_devp->current_len = 0; //fifo length
- init_waitqueue_head(&globalmem_devp->r_wait);/////////////////////
- init_waitqueue_head(&globalmem_devp->w_wait);////////////////////
- ywx@ywx:~/desktop/module/baohua/globalmem_fifo$ ls
- application globalmem.c.old globalmem.mod.c globalmem.o modules.order
- globalmem.c globalmem.ko globalmem.mod.o Makefile Module.symvers
- ywx@ywx:~/desktop/module/baohua/globalmem_fifo$ sudo insmod ./globalmem.ko
- [sudo] password for ywx:
- ywx@ywx:~/desktop/module/baohua/globalmem_fifo$ sudo mknod /dev/globalmem c 240
- root@ywx:/home/ywx/desktop/module/baohua/globalmem_fifo# cat /dev/globalmem &
- [1] 3008
- root@ywx:/home/ywx/desktop/module/baohua/globalmem_fifo# echo "i love linux" > /dev/globalmem
- root@ywx:/home/ywx/desktop/module/baohua/globalmem_fifo# i love linux
启动两个进程,一个进程 “cat /dev/globalmem”在后台执行,一个进程“echo "i love linux" >/dev/globalmem”在前台执行。当我们输入 echo 后,另一个进程cat 马上显示 globalmem设备中的数据。