《Linux内核的设计与实现》读书笔记(三)---进程管理
2016-11-24 21:08
274 查看
第3章 进程管理
进程与线程
进程是出于执行期的程序。线程是进程中活动的对象,每个线程都拥有一个独立的程序计数器、进程栈、和一组进程寄存器。(内核调度的对象是线程,不是进程)进程提供两种虚拟机制:虚拟处理器和虚拟内存。同一进程中的线程之间可以共享虚拟内存。
进程与线程的区别
进程的存放和表示(task_struct和thread_info)
进程描述符(task_struct)
进程描述符(一个结构体)中包含的数据能完整地描述一个正在执行的程序:它打开的文件、进程的地址空间、挂起的信号、进程的状态等等。1、分配进程描述符
Linux通过slab分配器分配task_struct结构,这样能达到对象的复用和缓存着色的目的(通过预先分配和重复使用task_struct,可以避免动态分配和释放所带来的资源消耗)。
2.6以前的内核:各个进程的task_struct存放在他们内核栈的尾端。
2.6及2.6以后:用slab分配器动态生成task_struct,只需要在内核栈的尾端创建一个struct thread_info。
thread_info结构中有个指向该任务实际task_struct的指针。
2、进程描述符的存放
内核通过唯一的进程标识值或PID来标识每个进程。
内核把每个进程的PID存放在它们各自的进程描述符中
内核访问任务通常需要获得指向其task_struct的指针
通过current宏查找当前正在运行进程的进程描述符
3、进程状态
TASK_RUNNING(运行)—在用户空间中执行的唯一可能的状态(因为在用户进程看来,它是独享处理器的,就不存在像内核空间那样的就绪态和执行态的区别了)
TASK_INTERRUPTIBLE(可中断)—等待事件发生或接收信号
TASK_UNINTERRUPTIBLE(不可中断)—对信号不做响应外和可中断相同
TASK_TRACED(被跟踪)
TASK_STOPPED(停止)—没有投入运行也不能投入运行
4、设置当前进程的状态
内核调整某个进程的状态
set_task_state(task,state); /*将任务task的状态设置为state*/
5、进程上下文
用户空间的进程陷入内核空间执行,称内核“代表进程执行”并处于进程上下文中。
系统调用和异常处理程序是对内核明确定义的接口。进程只有通过这些接口才能陷入内核执行—对内核的所有访问都必须通过这些接口。
6、进程家族树
进程间存在明显的继承关系
所有进程都是PID为1进程的后代
进程间的关系存放在进程描述符中
每个进程描述符都有一个parent父进程指针和一个children的子进程链表
进程的创建(通过fork(),实际上最终是clone())
进程的创建分为两个步骤:fork()和exec().fork():通过拷贝当前进程创建一个子进程
exec():负责读取可执行文件并将其载入地址空间开始运行
进程创建调用流程:
fork()/vfork()/_clone() -> clone() -> do_fork() -> copy_process()
copy_process()实现流程
调用dup_task_struct()为新进程分配内核栈,task_struct等,其中的内容与父进程相同。
check新进程(进程数目是否超出上限等)
清理新进程的信息(比如PID置0等),使之与父进程区别开。
新进程状态置为 TASK_UNINTERRUPTIBLE
更新task_struct的flags成员。
调用alloc_pid()为新进程分配一个有效的PID
根据clone()的参数标志,拷贝或共享相应的信息
做一些扫尾工作并返回新进程指针
写时拷贝
写时拷贝是一种可以推迟甚至免于拷贝数据的技术。Linux的fork()使用的就是写时拷贝页实现,此时内核并不复制地址空间,以只读的方式共享,资源的复制只有在需要写入(父进程或子进程需要写入)的时候才进行。优势:一般的情况下,进程的创建马上就会运行一个可执行的文件,可以避免拷贝大量不会被使用的数据。
fork()
内核有意选择子进程首先执行(但是并非总能如此),因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能开始向地址空间写入。vfork()
除了不拷贝父进程页表项外,vfork()系统调用和fork()的功能相同。在进程创建的过程中,子进程先开始执行后,父进程不是马上恢复执行,而是一直等待,直到子进程通过vfork_done指针像它发送信号。
fork()和vfork()的区别
fork():子进程拷贝父进程的数据段和代码段vfork():子进程与父进程共享数据段
fork():父子进程的执行次序不确定
vfork():保证子进程先运行,在调用exec()或exit()之前与父进程数据共享,调用之后父进程才被调度运行
vfork()保证子进程先运行,在调用exec()或exit()之前与父进程数据共享,调用之后父进程才被调度运行,如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
线程在Linux中的实现
创建线程
创建线程和进程的步骤一样,只是最终传给clone()函数的参数不同。比如,通过一个普通的fork来创建进程,相当于:clone(SIGCHLD, 0)
创建一个和父进程共享地址空间,文件系统资源,文件描述符和信号处理程序的进程,即一个线程:clone(CLONE_VM | CLONE_FS | CLONE_FILES |CLONE_SIGHAND, 0)
内核线程
内核线程和普通线程的区别在于内核线程没有独立的地址空间,它们只运行在内核空间,同样可以被调度,被抢占。进程终结
进程终结的流程
进程终结需要调用do_exit(),具体流程如下:设置task_struct中的标识成员设置为PF_EXITING
调用del_timer_sync()删除内核定时器, 确保没有定时器在排队和运行
调用exit_mm()释放进程占用的mm_struct
调用sem__exit(),使进程离开等待IPC信号的队列
调用exit_files()和exit_fs(),释放进程占用的文件描述符和文件系统资源
把task_struct的exit_code设置为进程的返回值
调用exit_notify()向父进程发送信号,并把自己的状态设为EXIT_ZOMBIE
切换到新进程继续执行
删除进程描述符
在调用了do_exit()后,尽管线程已经僵死不能再运行了,但是系统还保留了它的进程描述符。父进程收尸—wait()
父进程受到子进程发送的exit_notify()信号后,将该子进程的进程描述符和所有进程独享的资源全部删除。
孤儿进程
当父进程先于子进程结束,那么必须保证子进程能够找到新的父亲。子进程在调用exit_notify()时已经考虑到了这点。
如果子进
4000
程的父进程已经退出了,那么子进程在退出时,exit_notify()函数会先调用forget_original_parent(),然后再调用find_new_reaper()来寻找新的父进程。
find_new_reaper()函数先在当前线程组中找一个线程作为父亲,如果找不到,就让init做父进程。(init进程是在linux启动时就一直存在的)
相关文章推荐
- Linux内核设计与实现——读书笔记2:进程管理
- Linux内核设计与实现-第三章 进程管理
- 进程管理(Linux内核设计与实现 整理)
- Linux内核设计与实现----进程管理
- linux内核设计与实现(三) linux进程管理 之 进程描述—1
- Linux内核设计与实现 读书笔记(4)进程的调度
- linux内核设计与实现笔记之第三章进程管理
- 【Linux内核设计与实现】进程管理
- 《Linux内核的设计与实现》读书笔记(四)---进程调度
- Linux内核设计与实现之进程管理
- Linux内核设计与实现(三) linux进程管理 之 进程创建-2
- Linux内核设计与实现 学习笔记(二)进程管理
- linux内核设计与实现 进程管理 访问子进程的方法详解
- Linux内核设计与实现笔记--chapter3 进程管理
- linux内核设计与实现【第三版】摘记----第三章:进程管理
- Linux内核设计与实现----进程管理
- linux内核设计与实现(进程管理、进程调度读书笔记)
- Linux内核设计与实现 读书笔记(2)内核开发的准备
- Linux进程(Linux内核设计与实现学习笔记)
- Linux内核的设计与实现 读书笔记(7)中断处理