linux 资源竞争 引起的 阻塞 休眠 以及 驱动部分的工作

来源:互联网 发布:淘宝韩国服装代购 编辑:程序博客网 时间:2024/06/06 08:24

该笔记仅仅整理大体的数据结构思路有个整体的把握,不会进入具体的代码的分析。

多个进程对资源的并发访问时需要一个处理这些并发申请的中间层。

1)阻塞 休眠 和唤醒的基本机理

    休眠:由于硬件事件或者有其他进程正使用资源,当前对该资源的所有申请的进程可以选择等待该资源,内核会把该进程状态设置为!TASK_RUNNING 并调用schedule();   调用其他就绪的进程。一般某一资源对应一条休眠链表,进程先建立一个空链表wait_queque_head_t变量p 该变量通常和某一资源判断的condition相对应并用__wait_queue结构包含该空表头,然后把当前进程执行地址即进程描述符,休眠的方式(有可中断休眠链表和不可中断休眠链表,不同的休眠方式纵使是链表头name相同也会有不同的链表),唤醒函数地址等初始化到__wait_queue结构并把单节点插入到p链表的头部下,形成了P表头的双向链表,每个进程的一个休眠作为一个链表的节点挂接在p链表内,当wake up时遍历该链表并调用唤醒和释放所有的节点。

   进程需要的做的是把该进程和链表头wait_queue_head_t *p 挂钩,还有和判断事件的condition联系注册到宏

wait_event_interruptible(wq, condition)      内即可

 休眠链表wait_queue_head_t wq   并没有condition元素,应该是基于链表仅仅提供构建有共同的wq 的进程等待的队列的数据结构,不涉及到具体的事件而且单个进程调用wait_event_interruptible插入节点后还需要再度确认condition为假再调用schedule()完成该进程的睡眠,否则可能错过唤醒信号因为代码执行到添加链表前其进程是可被打断的。


链表头为typedef struct __wait_queue_head wait_queue_head_t,表头包含在__wait_queue如下:

struct __wait_queue {
         unsigned int flags;
#define WQ_FLAG_EXCLUSIVE        0x01
         void *private;//指向当前进程的描述符,和当前表项对应
         wait_queue_func_t func;//回调,唤醒时调用
         struct list_head task_list;//链表节点
};

   唤醒: 资源可用时,其他进程调用__wake_up 遍历该链表的各个__wait_queue,并调用func 唤醒对应的进程,若是sync 则直接调度切换到该被唤醒的进程并执行(???猜测???),否则返回到该进程继续执行,此时被唤醒的进程处于TASK_RUNNING。注意:加入下一进程又独占资源时,则其他刚唤醒的队列将再度休眠,如此反复,设计时需要考虑休眠队列在进程多是建立和释放时的大开销,可以换用独占的队列。

2) poll 和 select , epoll  

 实现

    应用需要一次检查多个io或者设备是否可用时,不断的轮询会因为检查某个设备而忽略并错失了其他设备事件,这种情况最好是先把对应的设备和事件记录在poll table 里,当所有等待的资源均不可用时该进程睡眠,知道某一个资源可用或者最大时延是则触发该事件。

    应用发出多个资源(设备)的申请,等待poll table 的事件触发

    不同资源(设备)负责把把对应的文件描述符放到等待队列中并返回当前资源情况编码,并不会阻塞。

 区别:

poll  和 select  

应用程序调用select,进入内核调用sys_select,做些简单初始化工作,接着进入 core_sys_select,此函数主要工作是把描述符集合从用户空间复制到内核空间, 最终进入do_select,完成其主要的功能。do_select里,调用 poll_initwait,主要工作是注册poll_wait的回调函数为__pollwait,当在设备驱动的poll回调函数里调用poll_wait,其实就是调用__pollwait, __pollwait的主要工作是把当前进程挂载到等待队列里,当等待的事件到来就会唤醒此进程。接着执行for循环,循环里首先遍历每个文件描述符,调用对应描述符的poll回调函数,检测是否就绪,遍历完所有描述符之后,只要有描述符处于就绪状态,信号中断,出错或者超时,就退出循环,否则会调用schedule_xxx函数,让当前进程睡眠,一直到超时或者有描述符就绪被唤醒。接着又会再次遍历每个描述符,调用poll再次检测。如此循环,直到符合条件才会退出。(摘自http://blog.csdn.net/fanxiushu/article/details/8600826) 看的出来文件越多性能越低。

epoll:

epoll分为三个函数 epoll_create,epoll_ctl, epoll_wait 。他们的实现在 eventpoll.c代码里。
    epoll_create创建epoll设备,用来管理所有添加进去的描述符,epoll_ctl 用来添加新的描述符,修改或者删除描述符。epoll_wait等待描述符事件。epoll_wait的等待已经不再是轮训方式的等待了,epoll内部有个描述符就绪队列,epoll_wait只检测这个队列即可,他采用睡眠一会检测一下的方式,如果发现描述符就绪队列不为空,就把此队列中的描述符copy到用户空间,然后返回。

描述符就绪队列里的数据又是从何而来的?  原来使用 epoll_ctl添加新描述符时候,epoll_ctl内核实现里会修改两个回调函数,一个是 poll_table结构里的qproc回调函数指针, 在 select中是 __pollwait函数,在epoll中换成 ep_ptable_queue_proc,当在epoll_ctl中调用新添加的描述符的poll回调时候,底层驱动就会调用 poll_wait添加等待队列,底层驱动调用poll_wait时候,其实就是调用ep_ptable_queue_proc,此函数会修改等待队列的回调函数为 ep_poll_callback, 并加入到等待队列头里;一旦底层驱动发现数据就绪,就会调用wake_up唤醒等待队列,从而 ep_poll_callback将被调用,在ep_poll_callback中 会把这个就绪的描述符添加到 epoll的描述符就绪队列里,并同时唤醒 epoll_wait 所在的进程。

(摘自http://blog.csdn.net/fanxiushu/article/details/8600826

异步:为了等待某一事件或资源应用可以休眠等待或者轮询,这两种方案均在资源不可用时应用进程休眠,最好的方案是资源可用时自动产生一个信号通知应用,应用执行相应的处理程序,即赋予应用进程以中断策略提高应用的效率。

       当某一设备的硬件事件发生时,触发设备驱动调用kill_fasync()根据该设备号查询singly linked list 假如该设备的为fasync(即在该链表上注册了该设备),找到该设备fasync_struct节点从而找到接受该事件的进程id,并向该进程发送SIGIIO。

驱动部分:注册进程id 和 fasync_struct ; 实现kill_fasync 函数。

    



   

0 0
原创粉丝点击