笔记,摘录自<深入理解linux内核>
1.进程是程序执行时的一个实例,从内核观点看,进程的目的就是担当分配系统资源(CPU时间,内存等)的实体。
2.当一个进程创建时,它几乎与父进程相同。它接受父进程地址空间的一个拷贝,并从进程创建系统调用的下一条指令开始执行与父进程相同的代码。尽管父子进程可以共享含有程序代码的页,但是他们各自有独立的数据拷贝(栈和堆),因此子进程对一个内存单元的修改对父进程是不可见的(反之亦然)。
3.现代Unix系统支持多线程应用程序--拥有很多相对独立执行流的用户程序共享应用程序的大部分数据结构。在这样的系统钟,一个进程由几个用户线程组成,每个线程都代表进程的一个执行流,现在,带部分多线程应用程序都是用pthread(POSIX thread)库的标准库函数集编写的。
4.Linux内核的早期版本没有提供多线程应用的支持,从内核观点看,多线程应用程序仅仅是一个普通进程,多线程应用程序多个执行流的创建,处理,调度整个都是在用户态进行的(通常使用POSIX兼容的pthread库)(即多对一模型)。缺点即一个用户线程阻塞会导致整个进程阻塞。
Linux使用轻量级进程(lightweight process,LWP)对多线程应用程序提供更好的支持,两个轻量级进程基本上可以共享一些资源,诸如地址空间,打开的文件等等。只要其中一个修改共享资源,另一个就立即查看这种修改。当然,当两个线程访问共享资源时就必须同步它们自己。
实现多线程应用程序的一个简单方式就是把轻量级进程与每个线程关联起来(一对一或多对多),这样,线程之间可以共享资源,同时每个线程都可以由内核独立调度。
POSIX兼容的pthread库使用Linux轻量级进程的有LinuxThreads(实现水平较低),NGPT(已经停止开发),NPTL。
5.为了管理进程,每个进程都维护了进程描述符
6.进程状态:描述了进程当前所处的状态,由一组标志组成,状态是互斥的。可运行状态(TASK_RUNNING)(正在执行或者准备执行),可中断的等待状态(TASK_INTERRUPTIBLE),不可中断的等待状态(TASK_UNINTERRUPTIBLE)(例如,当进程打开一个设备文件,其相应的设备驱动程序开始探测相应的硬件设备时会用到,探测完成以前,设备驱动程序不能被终端,否则,硬件设备会处于不可预知的状态),暂停状态(TASK_STOPPED),跟踪状态(TASK_TRACED)(例如被debugger程序暂停),僵死状态(EXIT_ZOMBIE),僵死撤销状态(EXIT_DEAD)。
7.进程ID(内核通过管理一个pidmap-array位图来表示当前已分配和闲置的PID,bitmap?),一个线程组钟的所有线程使用和该线程组的领头线程相同的PID,它被存入进程描述符的tgid字段中。
8.内核态的进程访问处于处于内核数据段的栈,不同于用户态的进程所用的栈。从用户态刚切换到内核态以后,进程的内核栈总是空的。
9.进程链表的数据结构是双向链表,每个task_struct结构都包含一个list_head类型的tasks字段,这个类型的prev和next字段分别指向前面和后面的task_struct元素。进程链表的头是init_task描述符,它是所谓的0进程或swapper进程的进程描述符(内核线程)。
早期Linux版本把所有的可运行进程都放在同一个叫做运行队列(runqueue)的链表钟,由于维持链表钟的进程按优先级排序开销过大,因此不得不为选择最佳可运行进程而扫描整个队列。Linux 2.6的优化方式是建立多个可运行进程链表,每个优先级k对应一个可运行进程链表,每个进程按照对应优先级插入对应的可运行链表。
10.进程具有父子关系,一个进程创建的多个子进程之间有兄弟关系,进程0和进程1是内核创建的,进程1(init)是所有进程的祖先。
11.内核需要从进程的PID导出对应的进程描述符指针,例如kill(),顺序扫描进程链表非常低效,因为内核引入了四个散列表(分别为pid 进程PID,tgid 线程组 leader PID,pgrp 进程组 leader PID,session 会话 leader PID,值为链表头节点)
12.等待队列实现了在事件上的条件等待,希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制权,因此等待队列表示一组睡眠的进程,当某一条件为真时,由内核唤醒它们。
等待队列由双向链表实现,同时为了保证同步,通过等待队列头中的lock自旋锁达到的。
睡眠进程分为互斥进程和非互斥进程,互斥进程由内核有选择的唤醒,而非互斥进程总是由内核在事件发生时唤醒。
13.每个进程都有一组相关的资源限制,限制指定了进程能使用的系统资源数量。
14.进程切换只发生在内核态,在执行进程切换之前,用户态进程使用的所有寄存器内容都已经保存在内核态堆栈上。
硬件上下文:所有进程必须共享CPU寄存器,因此恢复一个进程的执行之前,内核必须确保每个寄存器装入了挂起进程时的值。寄存器的一组数据称为硬件上下文(hardware context)。
进程切换对应schedule()函数,从本质上来说,进程切换由两步组成,1.切换页全局目录以安装一个新的地址空间,2.切换内核态堆栈和硬件上下文。
15.传统的Unix操作系统以统一的方式对待所有的进程,子进程复制父进程所拥有的资源,这种方式使得进程的创建非常慢并且效率低,因为子进程需要拷贝父进程的整个空间。现在Unix内核通过三种不同的机制解决了这个问题:1.写时复制,允许父子进程读相同的物理页,只在写时进行拷贝(懒拷贝?)。2.轻量级进程允许父子进程共享每个进程在内核的很多数据结构(clone())。3.vfork()系统调用创建的进程能共享其父进程的内存地址空间。
16.因为一些系统进程只运行在内核态,所以现在操作系统把它们的函数委托给内核线程(kernel thread),内核线程不受不必要的用户态上下文的拖累。