Linux内核学习笔记

一、进程与线程

定义

进程就是处于执行期的程序。实际上,进程就是正在执行代码的实际结果。
线程是在进程中活动的对象,每个线程都拥有独立的程序计数器,进程栈以及一组进程寄存器。内核的调度对象是线程,而不是
进程。

   
进程是处于执行期的程序,但是并不仅仅局限于一段可执行程序代码。通常,进程还要包含其他资源,像打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有内存映射的内存地址空间及一个或多个执行线程,当然还包括用来存放全局变量的数据段等。在Linux内核中,进程也通常叫做任务

进程的两种虚拟机制

  1. 虚拟处理器:每个线程独有,不能共享
  2. 虚拟内存:同一个进程中的线程可以共享

   
执行线程,简称线程,是在进程中活动的对象。每个线程都拥有一个独立的程序计数器、进程栈和一组进程寄存器。内核调度的对象是线程,而不是进程。在传统的UNIX系统中,一个进程只包含一个线程,但在现在的系统中,包含多个线程的多线程程序司空见惯。在Linux系统中,线程和进程并不特别区分,对Linux而言,线程是一种特殊的进程

进程描述符及任务结构

  • 任务队列:存放进程列表的双向循环链表
  • task_struct:进程描述符,包含一个具体进程的所有信息。2.6以后的版本通过slab动态生成task_struct。
  • thread_info:线程描述符,

   
Linux实现线程的机制很独特。从内核角度来说,它并没有线程这个概念。Linux把所有的线程都当做进程来实现。内核并没有准备特别的调度算法或是定义特别的数据结构来表征线程。相反,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一隶属于自己的
task_struct ,所以在内核中,它看起来就像是一个普通的进程。

PID

唯一的进程标志值。int类型,为了与老版本的Unix和Linux兼容,PID的最大值默认设置为32768,这个值最大可增加到400万。进程的PID存放在进程描述符中。

 

进程状态

进程描述符中的state域记录进程当前的状态,进程一共有五中状态,分别为:

  • TASK_RUNNING 运行
  • TASK_INTERRUPTIBLE 可中断
  • TASK_UNINTERRUPTIBLE 不可中断
  • __TASK_TRACED 被其他进程跟踪的进程
  • __TASK_STOPPED 进程停止执行

二、进程描述符及任务结构

进程上下文

通常进程的代码在用户空间执行,当执行了系统调用或触发了某个异常时,它就陷入了内核空间。此时,我们称内核处于进程上下文中。

  1)进程描述符 

进程创建

  1. 写时拷贝,父子进程共享同一个地址空间,将页的拷贝推迟到实际发生写入时才进行。这个优化可以避免创建进程时拷贝大量不被使用的数据。
  2. 在进程中调用fork()会通过复制一个现有进程来创建一个新进程,调用fork()的进程是父进程,创建的进程是子进程。fork()函数从内核返回两次,一次是返回父进程,另一次返回子进程。Linux通过
    clone(SIGCHLD, 0);系统调用实现fork()。
  3. vfork()
    不拷贝父进程的页表项,其它与fork功能相同。系统实现:clone(CLONE_VFORK
    | CLONE_VM | SIGCHLD, 0);
  4. exec()这组函数可以创建新的地址空间,并把新的程序载入其中。

   
内核把进程的列表存放在任务队列中,任务队列是一个双向循环链表如图1所示。链表中每一项都是类型为
task_struct 的结构体,被称为 进程描述符,该结构定义在
<linux/sched.h>文件中。进程描述符中包含一个具体进程的所有信息。进程描述符中包含的数据能完整地描述一个正在执行的程序:它打开的文件、进程的地址空间、挂起的信号、进程的状态以及其他信息。 

线程实现

在Linux内核中线程看起来就是一个普通的进程,只是和其它一些进程共享某些资源,如地址空间。

  1. 创建线程同样使用clone实现,只是需要传递一些参数标志来指明需要共享的资源:clone(CLONE_VM
    | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);
  2. 内核线程没有独立的地址空间,只在内核空间运行,不切换到用户空间上去,只能由内核线程创建。

图片 1

进程终结

当一个进程终结时必须释放它所占有的资源。进程主动终结发生在进程调用exit()系统调用时,当然它还有可能被动终结。

  • 删除进程描述符:在调用do_exit()之后,尽管线程已经僵死不能再运行了,但系统还保留了它的进程描述符,在父进程获得已终结的子进程的信息或通知内核它不关注那些信息后,子进程的task_struct结构才释放。
  • 孤儿进程造成的进退维谷:由于进程退出时需要父进程通知父进程释放子进程的task_struct,如果一个进程找不到父进程就会在退出时永远处于僵死状态。因此要在父进程退出时为每一个子进程找到一个新的父亲,方法是给子进程在当前线程组内找一个线程作为父亲,如果不行就让init做它们的父进程。

图1 进程描述符及任务队列

    Linux通过slab分配器分配 task_struct
结构,这样能达到对象复用和缓存着色的目的,为了找到
task_struct,只需在栈底(对于向下增长的栈)或栈顶(对于向上增长的栈)创建一个新的结构
struct thread_info,该结构存放着指向任务实际 task_struct
的指针。结构的定义如下:

struct thread_info{
    struct task_struct     *task;
    struct exec_domain     *exec_domain;
    _u32                   flags;
    _u32                   status;
    _u32                   cpu;
    int                    preempt_count;
    mm_segment_t           addr_limit;
    struct restart_block   restart_block;
    void                   *sysenter_return;
    int                    uaccess_err;
};

2)进程状态    

发表评论

电子邮件地址不会被公开。 必填项已用*标注