响应。因为一些系统进程只运行在内核态,现代操作系统把它们的函数委托给内核线程(Kernel Thread),内核线程不受不必要的用户态上下文的拖累。
定时器任务队列:任务队列属于是半底bottom half操作的替代品,主要有调度队列、定时器队列和及时队列等三种任务队列(除了这三种系统已接管的特定任务队列外,你自己也可随心所欲的建立自己的任务队列,当然这时你要自己调度它)。我们使用的定时器队列会在每次时钟滴答时得到处理,它处于中断上下问中处理的。
上述三种内核任务存在如下竞争关系——系统调用和内核线程这种运行在进程上下文中的内核任务可能和各种内核任务并发,除了中断(定时器任务队列属于软中断范畴)抢占它产生并发外,它是有可能自发性地主动睡眠(比如在一些阻塞性的操作中),放弃处理器,重新调度其它任务,所以系统调用和内核线程除会与定时器任务队列发生竞争,也会与其他(包括自己)系统调用与内核线程发生竞争。 基本函数
我们主要的共享资源是链表(mine),操作它的内核任务有三种:一个是100个内核线程(sharelist),它们负责从表头将新节点(struct my_struct)插入链表。二是定时器任务(qt_task),它负责每个时钟滴答时从链表头删除一个节点。三是系统调用(由rmmod命令调用的share_exit),它负责销毁链表并卸载模块。
我们利用模块(sharelist.o)实现上述场景。加载模块时会建立定时器任务列队,并将要执行的任务(task.rounting=qt_task)插入定时器队列(tq_timer),然后反复调度执行(但别不停的执行)。与此同时利用系统中的keventd内核线程(它的目的是执行任务队列,由schedule_task激活,PID=2),创建100个内核线程(创建函数kernel_thread)执行插入链表的工作(由sharelist完成)——但当链表长度超过100时,则从链表尾删除节点。最后当你需要卸载模块时,调用share_exit函数销毁整个链表,并做一些诸如销毁我们建立的内核进程的收尾工作。
另外我们要注意在程序中该如何保护我们的链表。上述场景中存在的内核并发包括——内核线程之间的并发、内核任务与定时器任务的并发。要知道内核线程执行在进程上下文中,而定时器任务属于下半部分,执行在中断上下文中。在这两部分交错执行中进行保护则需要采用自旋锁。我们例子中使用了spin_lock_bh()锁在内核线程的执行路径中对链表进行保护;在下半部分由于任务队列是串行执行并且不能被内核任务或系统调用打断,所以不必加锁。另外在卸载模块时,删除链表中仍然存在系统调用与下半部分的并发可能,因此也需要按上述方式加锁。
除了对共享链表访问使用自旋锁以外,还有两个需要同步的地方,一是计数(count),该变量属于原子类型,用于记录链表接点的id。另外一个是利用信号量同步内核创建线程,调度keventd后执行被堵塞住(down),等内核线程实际启动后,才可继续执行(up)。下面的图给出了个任务的基本关系和对链表的操作。
Sharelist
该函数是作为内核线程由keventd调度执行的,作用是向链表中加如新节点(如果到达100个节点则删除尾部节点,因为我们不能无限制耗用内核内存),为了防止定时器任务队列抢占执行时,造成数据链表数据不一致,所以需要在操作链表期间进行同步保护——加锁,即spin_lock_bh()禁止或spin_unlock_bh开启定时器任务执行权能。 start_kthread
顾名思义,该函数用来触发执行内核线程Sharelist的封装函数。我们的目的是用keventd[1]来启动内核线程。我们不直接利用kernel_thread启动内核线程,而要交由keventd内核来间接启动sharelist的原因是——防止直接启用sharelist内核线程时会继承用户父进程的“不良”属性(如insmod等的属性),影响其执行,所以我们利用keventd——它没有“前科”——启动sharelist则可保证sharelist不会染上父进程的“不良习惯”。
该函数首先必须对任务队列进行初始化——如初始化调度任务队列头;注册sharelist触发函数;将任务加如调度任务队列(以等待keventd执行调度)。
tq.sync=0;
INIT_LIST_HEAD(&tq.list); tq.routine=kthread_launcher; tq.data=NULL; schedule_task(&tq);
kthread_launcher
该函数作用仅仅是通过kernel_thread方法启动内核线程sharelist。注意为了能依次建立(且启动)内核线程,我们start_kthread函数会在将任务加入调度队列后利用信号量自我堵塞(down(&sem)),直到在内核线程执行后才解除堵塞up(&sem),这种同步保证了串行创建内核线程。
qt_task
该函数作为定时器任务运行,删除链表节点。为了防止不知节制的删除,我们指定删除次数为count=1200次。
定时器任务队列为tq_timer,任务结构为struct tq_struct task,具体任务函数
为qt_task。将任务加入到定时器队列需要调用queue_task(&task,&tq_timer)——没次执行定时器任务前都需要将任务填加到队列中。
share_init
该函数是我们的模块注册函数,也是通过它启动我们的定时器任务和内核线程。它先初始化定时器任务队列,注册定时器任务qt_task:
init_waitqueue_head(&queue);
task.routine=(void*)qt_task;
task.data=NULL;
queue_task(&task,&tq_timer);
然后依次启动1000个内核线程start_kthread()。到此我们开始对链表进行操作。
share_exit
该函数是模块注销函数,它和注册函数一样属于系统调用,负责销毁链表。由于销毁时刻内核线程与定时器任务都在运行,所以作为一个系统调用操作链表时,应该进行同步保护,所以要锁住链表,在这里就是停止定时器任务队列——spin_lock_bh(同时也会停止内核线程对链表的操作,因为自旋锁保证了任务执行的串行化,此刻其它任务没机会执行了),当链表销毁重新打开自旋锁允许任务队列运行。
除了对共享链表访问使用自旋锁以外,还有两个需要同步的地方,一是计数(count),该变量属于原子类型,用于记录链表接点的id
share_exit
sharelist
链表
qt_task kevent
start_kthread
进程上下文
中断上下文