进程在Linux内核中的角色扮演


在Linux内核中,内核将进程、线程和内核线程一视同仁,即内核使用唯一的数据结构task_struct来分别表示他们;内核使用相同的调度算法对这三者进行调度;并且内核也使用同一个函数do_fork()来分别创建这三种执行线程(thread of execution)。执行线程通常是指任何正在执行的代码实例,比如一个内核线程,一个中断处理程序或一个进入内核的进程。

这样处理无疑是简洁方便的,并且内核在统一处理这三者之余并没有失去他们本身所具有的特性。本文将结合进程、线程和内核线程的特性浅谈进程在内核中的角色扮演问题。

1.进程描述符task_struct的多角色扮演

上述三种执行线程在内核中都使用统一的数据结构task_struct来表示。task_struct结构即所谓的进程描述符,它包含了与一个进程相关的所有信息。进程描述符中不仅包含了许多描述进程属性的字段,而且还有一系列指向其他数据结构的指针。下面将简单介绍进程描述符中几个比较特殊的字段,它们分别指向代表进程所拥有的资源的数据结构。

mm字段:指向mm_struct结构的指针,该类型用来描述进程整个的虚拟地址空间。

fs字段:指向fs_struct结构的指针,该类型用来描述进程所在文件系统的根目录和当前进程所在的目录信息。

files字段:指向files_struct结构的指针,该类型用来描述当前进程所打开文件的信息。

signal字段:指向signal_struct结构(信号描述符)的指针,该类型用来描述进程所能处理的信号。

对于普通进程来说,上述字段分别指向具体的数据结构以表示该进程所拥有的资源。

对应每个线程而言,内核通过轻量级进程与其进行关联。轻量级进程之所轻量,是因为它与其他进程共享上述所提及的进程资源。比如进程A创建了线程B,则B线程会在内核中对应一个轻量级进程。这个轻量级进程很自然的对应一个进程描述符,只不过B线程的进程描述符中的某些代表资源指针会和A进程中对应的字段指向同一个数据结构,这样就实现了多线程之间的资源共享。

由于内核线程只运行在内核态,并且只能由其他内核线程创建,所以内核线程并不需要和普通进程那样的独立地址空间。因此内核线程的进程描述符中的mm指针即为NULL。内核线程是否共享父内核线程的某些资源,则通过向内核线程创建函数kernel_thread()传递参数来决定。

通过上面的分析可以发现,内核中使用统一的进程描述符来表示进程、线程和内核线程,根据他们不同的特性,其进程描述符中某些代表资源的字段的指向会有所不同,以实现扮演不同角色。

2. do_fork()的多角色扮演

进程、线程以及内核线程都有对应的创建函数,不过这三者所对应的创建函数最终在内核都是由do_fork()进行创建的。do_fork()函数的原型如下:

该函数的参数个数是固定的,每个参数的功能如下:

clone_flags:代表进程各种特性的标志。低字节指定子进程结束时发送给父进程的信号代码,一般为SIGCHLD信号,剩余三个字节是若干个标志或运算的结果。

stack_start:子进程用户态堆栈的指针,该参数会被赋值给子进程的esp寄存器。

regs:指向通用寄存器值的指针,当进程从用户态切换到内核态时通用寄存器中的值会被保存到内核态堆栈中。

stack_size:未被使用,默认值为0。

parent_tidptr:该子进程的父进程用户态变量的地址,仅当CLONE_PARENT_SETTID被设置时有效。

child_tidptr:该子进程用户态变量的地址,仅当CLONE_CHILD_SETTID被设置时有效。

既然进程、线程和内核线程在内核中都是通过do_fork()完成创建的,那么do_fork()是如何体现其功能的多样性?其实,clone_flags参数在这里起到了关键作用,通过选取不同的标志,从而保证了do_fork()函数实现多角色——创建进程、线程和内核线程——功能的实现。clone_flags参数可取的标志很多,下面只介绍几个与本文相关的标志。

CLONE_VIM:子进程共享父进程内存描述符和所有的页表。

CLONE_FS:子进程共享父进程所在文件系统的根目录和当前工作目录。

CLONE_FILES:子进程共享父进程打开的文件。

CLONE_SIGHAND:子进程共享父进程的信号处理程序、阻塞信号和挂起的信号。使用该标志必须同时设置CLONE_VM标志。

如果创建子进程时设置了上述标志,那么子进程会共享这些标志所代表的父进程资源。

  • 1
  • 2
  • 下一页

相关内容