Linux中的线程
在Linux 2.4版以前,线程的实现和管理方式就是完全按照进程方式实现的;在Linux 2.6之前,内核并不支持线程的概念,仅通过轻量级进程(Lightweight Process)模拟线程;轻量级进程是建立在内核之上并由内核支持的用户线程,它是内核线程的高度抽象,每一个轻量级进程都与一个特定的内核线程关联。内核线程只能由内核管理并像普通进程一样被调度。这种模型最大的特点是线程调度由内核完成了,而其他线程操作(同步、取消)等都是核外的线程库(Linux Thread)函数完成的。
为了完全兼容Posix标准,Linux 2.6首先对内核进行了改进,引入了线程组的概念(仍然用轻量级进程表示线程),有了这个概念就可以将一组线程组织称为一个进程,不过内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程;相反,线程仅仅被视为一个与其他进程(概念上应该是线程)共享某些资源的进程(概念上应该是线程)。在实现上主要的改变就是在task_struct中加入tgid字段,这个字段就是用于表示线程组id的字段。在用户线程库方面,也使用NPTL代替Linux Thread,不同调度模型上仍然采用 1 对 1
模型。
进程的实现是调用fork系统调用:pid_t fork(void);
,线程的实现是调用clone系统调用:int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...)
。与标准 fork()
相比,线程带来的开销非常小,内核无需单独复制进程的内存空间或文件描写叙述符等等。这就节省了大量的CPU时间,使得线程创建比新进程创建快上十到一百倍,能够大量使用线程而无需太过于操心带来的CPU或内存不足。无论是fork、vfork、kthread_create最后都是要调用do_fork,而do_fork就是根据不同的函数参数,对一个进程所需的资源进行分配。
内核线程
内核线程是由内核自己创建的线程,也叫做守护线程(Deamon),在终端上用命令 ps -Al
列出的所有进程中,名字以k开关以d结尾的往往都是内核线程,比如kthreadd、kswapd等。与用户线程相比,它们都由 do_fork()
创建,每个线程都有独立的task_struct和内核栈;也都参与调度,内核线程也有优先级,会被调度器平等地换入换出。二者的不同之处在于,内核线程只工作在内核态中;而用户线程则既可以运行在内核态(执行系统调用时),也可以运行在用户态;内核线程没有用户空间,所以对于一个内核线程来说,它的0~3G的内存空间是空白的,它的 current->mm
是空的,与内核使用同一张页表;而用户线程则可以看到完整的0~4G内存空间。
在Linux内核启动的最后阶段,系统会创建两个内核线程,一个是init,一个是kthreadd。其中init线程的作用是运行文件系统上的一系列”init”脚本,并启动shell进程,所以init线程称得上是系统中所有用户进程的祖先,它的pid是1。kthreadd线程是内核的守护线程,在内核正常工作时,它永远不退出,是一个死循环,它的pid是2。