全部博文(321)
分类: 嵌入式
2013-03-07 22:37:32
一、进程的状态
a) 就绪:从等待过来的,当给他分配处理器(CPU)和时间片,就能到达执行状态了。
b) 执行:可以转成就绪和等待状态。当正在执行的进程因发生某等待事件而无法执行,则进程由执行状态变为等待状态。当正在执行的进程,因时间片用完而被暂停执行,或在采用抢先式优先级调度算法的系统中,当有更高优先级的进程要运行而被迫让出处理机时,该进程便由执行状态转变为就绪状态。
c) 等待:不能马上进入执行状态,要先转为就绪。等到时间片和分配到CPU,才可以执行。
二、
TASK_RUNNING ---就绪
TASK_INTERRUPTIBLE ---可被信号打断的休眠
TASK_UNINTERRUPTIBLE---不可被信号打断的休眠
TASK_STOPPED---挂起
当进程处于TASK_INTERRUPTIBLE 或TASK_UNINTERRUPTIBLE 状态时,称进程 睡眠(进程阻塞)。
三、直接改变进程状态的方法:
a) DECLARE_WAITQUEUE(wait,current);//3current,本进程,把本进程定义成等待队列
b) __set_current_state(TASK_INTERRUPTIBLE)是阻塞时改变进程状态为睡眠
schedule( ); 调度其他进程执行,让出CPU,进入休眠,程序不再执行
c) current->state = TASK_INTERRUPTIBLE;
schedule( ); //让出CPU,进入休眠
被信号唤醒。
四、阻塞与非阻塞I/O----------------可通过信号唤醒(要做判断)。
阻塞操作:在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。 如果进程调用read,但没有数据或数据不足,进程阻塞。当数据到达时,进程被唤醒,并将数据返回给调用者。(没有数据可读)===阻塞效率更高。让出CPU给其他工作
非阻塞操作:在执行设备操作时若不能获得资源时并不挂起,它或者放弃,或者不停地查询直到可以进行操作为止。如果进程调用了write,但设备没有足够缓冲区,进程阻塞。当数据被读出设备后,缓冲区中空出部分空间,则唤醒进程。(没空间可写)
在Linux驱动程序设计中,可以使用等待队列来实现进程的阻塞。
Current:代表本进程
五、使用等待队列有3种方法:
1 亲自进行进程状态的改变和切换。---=====再schedule
2: 使用wait_event_interruptible(dev->r_wait,flag_r!=0);需要设置flag_r。---例子4globalfifo_2
3: 使用interruptible_sleep_on(&dev->r_wait);---例子4globalfifo_2
唤醒的时候都是wake_up_interruptible
流程:
1、定义并初始化一个等待队列,将进程状态改变为TASK_UNINTERRUPTIBLE(不能被信号打断)或TASK_INTERRUPTIBLE(可被信号打断),并将等待队列添加到等待队列头。
2、通过schedule()放弃CPU调度其他进程执行。
3、进程被其他地方唤醒,将等待队列移出等待对列头。
代码:
1、定义:
unsigned int current_len;//记录fifo中的数据的有效长度
wait_queue_head_t r_wait;//1阻塞读用的等待队列头
wait_queue_head_t w_wait;//1阻塞写用的等待队列头
2、初始化:
init_waitqueue_head(&globalmem_devp->r_wait);//2初始化读等待 队列头
init_waitqueue_head(&globalmem_devp->w_wait);//2初始化写等待 队列头
globalmem_devp->current_len = 0;
3、声明一个等待队列:
DECLARE_WAITQUEUE(wait,current);//current,本进程,把本进程定义 成等待队列
4、添加到等待队列:
add_wait_queue(&dev->r_wait,&wait);//4进入读等待队列头
5、判断是否可读或者可写:
if(dev->current_len == 0)//说明缓存GLOBALMEM_SIZE为0,无数据 可读
if(dev->current_len == GLOBALMEM_SIZE)//说明缓存GLOBALMEM_SIZE 为16字节,无空间进行写
6、设置成状态:
__set_current_state(TASK_INTERRUPTIBLE);//5是阻塞时改变进程状 态为睡眠
7、让出CPU,让其进入真正睡眠:
schedule();//6调度其他进程执行,让出CPU,进入休眠,程序不再执行
8、醒过来了。判断是信号唤醒的还是进程唤醒的,即判断是真的可读写还 是信号打断:
if(signal_pending(current))
9内存移动(read):
memcpy(dev->mem,dev->mem+count,dev->current_len-count);
10、唤醒:
读唤醒写:
wake_up_interruptible(&dev->w_wait);
写唤醒读:
wake_up_interruptible(&dev->r_wait);
附:使用等待队列有3种方法:
1:亲自进行进程状态的改变和切换。
2:使用wait_event_interruptible(dev->r_wait,flag_r!=0);需要设 置flag_r。
3:使用interruptible_sleep_on(&dev->r_wait);
2和3两种是不可中断的。===虽然简单,但是效率低,所以用上面方法 多。
I/O非阻塞(轮询)
一、使用非阻塞I/O,同时使用select()和poll()系统调用查询是否可对设备进行无阻塞操作访问。=========>内核,poll().
二、轮询:即不停的查询。
三、在项目中。主函数(main),即使只要检测读或者写其中一种,读和写集都必须设置.. 也就是说
FD_ZERO(&rfds)
FD_ZERO(&wfds);
FD_SET(fd, &rfds);
FD_SET(fd, &wfds);
select(fd + 1, &rfds, &wfds, NULL, NULL);及内核中的poll函数要写完整
四个都要写。缺一不可。在应用中根据实际情况需要来调用FD_ISSET及读写函数
四、项目工作原理:先把读的加到读集,写的加到写集。通过应用层select函数,与内核中的Poll进行联系。由于select在while(1)里面,所以说他会不停的来调用poll函数。而内核Poll函数通过if(dev->current_len != 0)// 有数据可读 和 if(dev->current_len != GLOBALMEM_SIZE)//数据可写。进行判断。通过返回值mask(掩码)返回给应用层的select,当返回的select是1或者2,就说明可以读或者写了。再通过应用层if(FD_ISSET(fd, &rfds))数据可读 和 if(FD_ISSET(fd, &wfds))数据可写。来判断是可读或者可写。当进入应用层read或者write时候。就证明内核函数中的read和write一定可以调用了。
struct timeval tv;===这个时间不准,有bug...
现象:一边读一边写,读完了写,写完了读。
代码:
1、poll函数中,添加到等待队列中
poll_wait(filp,&dev->r_wait,wait);
poll_wait(filp,&dev->w_wait,wait);
2、进行判断可读或者可写:
if(dev->current_len != 0)// 有数据可读
if(dev->current_len != GLOBALMEM_SIZE)//数据可写
返回掩码return mask;(即返回数据是可读还是可写)
补充:
Poll方法只是做一个登记,真正的阻塞发生在select.c 中的 do_select函数,处理流程:
1:初始化 poll_table 表
2:依次调用每个文件的 poll 方法
3:poll 方法调用的 poll_wait会把当前的进程挂到驱动提供的wait_queue_head_t中,相当于遍历了等待队列poll_wait里面是否有变化。
4:假如能读或能写或有信号,就返回
5:否则调用 schedule_timeout 睡眠
设备驱动中的poll()本身不会阻塞,但是poll()和select()系统调用(介于应用层和内核之间的那一层VFS)则会阻塞的等待文件描述符集合中的至少一个(可读,可写,或者可读可写)可访问或超时。
Pollin ---对应的read===(POLLIN | POLLRDNORM).
Pollout---对应的write===(POLLOUT | POLLWRNORM).