首页 理论教育实体服务单独执行|Linux内核与设备驱动

实体服务单独执行|Linux内核与设备驱动

【摘要】:从图5-38可见,Linux内核根据需要在不同的处理器上创建执行实体线程,来执行具体的work,这些work都是动态添加的。整个work queue的管理实体是work queue_struct,其中可以包含多个线程,分别执行相应的延时工作。这样,内核线程的主要功能和接口就进行了介绍,内核线程主要提供执行实体的创建和管理服务,具体的业务细节要由具体的模块提供操作接口执行。

1ᤫ工作队列work queue

Linux内核中很多模块会有一些延时操作的需求,由于内核中各种上下文能进行的操作不同,所以延时操作最简单的办法就是能够在单独的进程上下文中执行,这样可以进行各种复杂的操作而相对限制最少。如果让内核中各个模块的延时操作都要创建进程,则系统运行时会占用大量的资源,同时也增加了系统调度的开销。如何做才是最好的呢?对延时操作来说其抽象的概念是操作,而操作的软件化抽象就是函数。如果系统可以根据当前系统的运行情况动态地创建执行实体,而当模块有延时操作需要时直接将这些操作本身加入到系统提供的进程上执行,这样既满足了各个模块的功能需求,又可以使系统资源占用最少,兼顾了功能和性能两个方面。Linux内核提供的工作队列就实现了该功能,对各个模块来说,其需要系统进行的延时操作就是一个一个的“工作”。

Linux内核运行时工作队列状态如图5-38所示。

从图5-38可见,Linux内核根据需要在不同的处理器上创建执行实体线程,来执行具体的work,这些work都是动态添加的。整个work queue的管理实体是work queue_struct,其中可以包含多个线程,分别执行相应的延时工作。

Linux内核为系统提供了一些默认的work queue,具体如下:

978-7-111-49426-3-Chapter05-164.jpg

这些work queue会由不同的接口向其中添加工作,每个工作队列的执行特点也是不同的,system_wq会有多个任务执行,但是希望每个work不要消耗太长时间;system_long_wq类似于system_wq,但是允许长时间的work执行;system_nrt_wq确保work不会重入,并且不会在多个处理器上执行;system_unbound_wq则不会将work固定在某个处理器上执行,并将尽快尽力执行。通常默认使用的是system_wq,也可以根据需要通过queue_work将work加入到具体的work queue上来执行。

978-7-111-49426-3-Chapter05-165.jpg

图5-38 工作队列运行状态

由于系统已经提供了各种类型的工作队列,对模块开发来说,延时操作最重要的就是work了,系统对work管理的结构是work_struct,细节如下:

978-7-111-49426-3-Chapter05-166.jpg

结构很简单,其中对模块最重要的是操作接口work_func_t,具体定义如下:

978-7-111-49426-3-Chapter05-167.jpg

从中可见传入的参数并不是模块相关的,那么模块如何获得其管理实体的信息呢?方法就是将work_struct做成静态的形式并嵌入模块管理实体中,这样通过container_of就可以获得相关的管理实体。为此工作队列框架提供了相应的初始化宏:

978-7-111-49426-3-Chapter05-168.jpg

工作队列框架还提供了一系列接口函数方便各个模块使用,接口说明如下:

●create_workqueue:用于创建一个workqueue队列,为系统中的每个CPU都创建一个内核线程。

●create_singlethread_workqueue:用于创建workqueue,只创建一个内核线程。

●destroy_workqueue:释放workqueue队列。

●schedule_work:调度执行一个具体的任务work_struct,执行的任务将会被挂入Linux系统提供的system_wq:名字是events。

●schedule_delayed_work:延迟一定时间去执行一个具体的任务,功能与schedule_work类似,多了一个延迟时间。

●queue_work:调度执行一个指定workqueue中的任务。

●queue_delayed_work:延迟调度执行一个指定workqueue中的任务,功能与queue_work类似,输入参数多了一个delay。

●queue_work_on:调度执行一个指定workqueue中的任务,并在指定的处理器上执行。(www.chuimin.cn)

对于通常的模块来说不需要创建工作队列,而是通过INIT_WORK来初始化静态的work_struct,在需要调度work执行时(如中断处理ISR中),通过相应的接口进行调度即可。

2ᤫ内核线程kernelth read

工作队列并不能满足所有的需求,另外工作队列本身也是需要一个真正的执行实体的承载,在内核中这个真正的执行实体就是内核线程kernelth read。在执行ps-aux命令时会看到如下信息:

978-7-111-49426-3-Chapter05-169.jpg

这些都是内核线程,之前在介绍中断时,介绍了中断处理也提供了在内核线程上执行操作的接口。

内核线程作为独立的调度实体,对于内核模块来说控制能力更强,所以很多复杂的功能都需要通过内核线程来执行。

下面对内核线程的主要接口进行说明:

978-7-111-49426-3-Chapter05-170.jpg

kthread_create用以产生内核线程,可以在所有处理器上运作,产生后的内核线程会等待被wake_up_process唤醒或是被kthread_stop终止。

978-7-111-49426-3-Chapter05-171.jpg

kthread_run主要用于产生并唤醒内核线程(开发者可以省去要呼叫wake_up_process的动作),其是基于kthread_create,所以产生的内核线程也不限于在特定的处理器上执行。

978-7-111-49426-3-Chapter05-172.jpg

kthread_bind用来将内核线程绑定到固定的CPU上,主要是通过设定CPU allowed bitm-ask来实现,所以在SMP的架构下,就可以指定给一个以上的处理器执行。

978-7-111-49426-3-Chapter05-173.jpg

kthread_stop用来暂停通过kthread_create产生的内核线程,并会等待内核线程结束,并传回函式threadfn的返回值。

通过这些接口可见,主要的内核线程都是通过kthread_create创建的,下面来看看其具体的细节。

978-7-111-49426-3-Chapter05-174.jpg

978-7-111-49426-3-Chapter05-175.jpg

可见,创建内核线程的主要任务是由kthreadd内核线程来实现的,kthreadd内核线程是系统的2号线程,主要的任务就是屏蔽体系结构相关的部分,为系统提供创建内核线程的服务,其与系统初始化相关的部分之前已经进行了介绍,其具体创建线程的操作是通过kernel_thread实现的,ARM内的实现如下:

978-7-111-49426-3-Chapter05-176.jpg

978-7-111-49426-3-Chapter05-177.jpg

可见,其主要是通过do_fork这一调度模块的接口创建的。

这样,内核线程的主要功能和接口就进行了介绍,内核线程主要提供执行实体的创建和管理服务,具体的业务细节要由具体的模块提供操作接口执行。