Linux内核可以看作一个服务进程(管理软硬件资源,响应用户进程的种种合理以及不合理的请求)。内核需要多个执行流并行,为了防止可能的阻塞,多线程化是必要的。
内核线程就是内核的分身,一个分身可以处理一件特定事情。Linux内核使用内核线程来将内核分成几个功能模块,像kswapd、kflushd等,这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫做多线程内核(Multi-Threads kernel )。
内核线程的调度由内核负责,一个内核线程处于阻塞状态时不影响其他的内核线程,因为其是调度的基本单位。这与用户线程是不一样的。
内核线程只运行在内核态,不受用户态上下文的拖累。
1.跟普通进程一样,内核线程也有优先级和被调度。 当和用户进程拥有相同的static_prio时,内核线程有机会得到更多的cpu资源
2.内核线程的bug直接影响内核,很容易搞死整个系统, 但是用户进程处在内核的管理下,其bug最严重的情况也只会把自己整崩溃
3.内核线程没有自己的地址空间,所以它们的”current->mm”都是空的;
4.内核线程只能在内核空间操作,不能与用户空间交互;
内核线程不需要访问用户空间内存,这是再好不过了。所以内核线程的task_struct的mm域为空.但是刚才说过,内核线程还有核心堆栈,没有mm怎么访问它的核心堆栈呢?这个核心堆栈跟task_struct的thread_info共享8k的空间,所以不用mm描述。
但是内核线程总要访问内核空间的其他内核啊,没有mm域毕竟是不行的。所以内核线程被调用时, 内核会将其task_strcut的active_mm指向前一个被调度出的进程的mm域, 在需要的时候,内核线程可以使用前一个进程的内存描述符。
因为内核线程不访问用户空间,只操作内核空间内存,而所有进程的内核空间都是一样的。这样就省下了一个mm域的内存。
在内核中,有两种方法可以生成内核线程,一种是使用kernel_thread()
接口,另一种是用kthread_create()
接口
先说kernel_thread接口,使用该接口创建的线程,必须在该线程中调用daemonize()
函数,这是因为只有当线程的父进程指向”Kthreadd”时,该线程才算是内核线程,而恰好daemonize()
函数主要工作便是将该线程的父进程改成“kthreadd”内核线程;默认情况下,调用deamonize()
后,会阻塞所有信号,如果想操作某个信号可以调用allow_signal()
函数
int kernel_thread(int (*fn)(void *), void *arg, unsigned long flags); // fn为线程函数,arg为线程函数参数,flags为标记 void daemonize(const char * name,...); // name为内核线程的名称
而kthread_create接口,则是标准的内核线程创建接口,只须调用该接口便可创建内核线程;默认创建的线程是存于不可运行的状态,所以需要在父进程中通过调用wake_up_process()
函数来启动该线程。
struct task_struct *kthread_create(int (*threadfn)(void *data),void *data, const char namefmt[], ...); //threadfn为线程函数;data为线程函数参数;namefmt为线程名称,可被格式化的, 类似printk一样传入某种格式的线程名
线程创建后,不会马上运行,而是需要将kthread_create()
返回的task_struct指针传给wake_up_process()
,然后通过此函数运行线程。
当然,还有一个创建并启动线程的函数:kthread_run
struct task_struct *kthread_run(int (*threadfn)(void *data), void *data, const char *namefmt, ...);
线程一旦启动起来后,会一直运行,除非该线程主动调用do_exit函数,或者其他的进程调用kthread_stop
函数,结束线程的运行。
int kthread_stop(struct task_struct *thread); kthread_stop() 通过发送信号给线程。 如果线程函数正在处理一个非常重要的任务,它不会被中断的。当然如果线程函数永远不返回并且不检查信号,它将永远都不会停止。 int wake_up_process(struct task_struct *p); //唤醒线程 struct task_struct *kthread_run(int (*threadfn)(void *data),void *data, const char namefmt[], ...);//是以上两个函数的功能的总和
因为线程也是进程,所以其结构体也是使用进程的结构体”struct task_struct”。
内核线程的退出当线程执行到函数末尾时会自动调用内核中do_exit()函数来退出或其他线程调用kthread_stop()来指定线程退出。