进程的管理
1、进程
进程:处于执行期的程序以及相关资源的总称,(进程是正在执行的程序代码的实时结果)包含其他资源:
- 打开的文件
- 挂起的信号
- 内核内部数据
- 处理器状态
- 一个或多个具有内存映射的内存地址空间
- 一个或多个线程
- 存放全局变量的数据段
执行线程:(线程)进程中活动的对象,拥有独立的程序计数器,进程栈和一组进程寄存器,是内核调度对象,在Linux中对进程和线程不做特别区分
操作系统中进程中的两种虚拟机制:虚拟处理器和虚拟内存
虚拟处理器:实际计算机里面可能有许多进程正在分享一个处理器,虚拟处理器会给进程一个假象,让进程认为自己有一个cpu
虚拟内存:让进程在分配和管理内存时觉得自己拥有整个系统所有的内存资源
线程之间可以共享虚拟内存,但是都拥有各自的虚拟处理器
2、进程描述符及任务结构
任务队列:用于内核中存放进程的列表,双向循环链表,链表中的每项类型是task_struct(进程描述符)
task_struct中包含的信息:
-
打开的文件
-
进程的地址空间
-
挂起的信号
-
进程的状态
1、分配进程描述符
在Linux中使用slab分配器动态生成task_struct,只需在栈底或栈顶创建一个新的结构struct thread_info,每个任务的thread_info结构在它的内核栈的尾端分配2、进程描述符的存放
内核中使用进程标识值或pid来标识进程,默认pid最大值是32768(short int的最大值)为了兼容老版本
根据硬件体系的不同对于获取task_struct指针方式不同: -
a:使用专门的寄存器来存放执行当前进程task_struct的指针加快访问速度
-
b:在x86中由于寄存器不富余,使用在内核栈尾创建thread_info结构,通过计算间接地查找task_struct结构
现在操作系统中可以通过修改/proc/sys/kernel/pid_max来提高上限3、进程的状态
内核中使用set_task_state(task, state)将进程task设置为state状态
进程的状态(五种),进程状态图:在linux的初始版本中是三种状态,运行,停止,准备 -1 unrunnable, 0 runnable, >0 stopped
a、TASK_RUNNING(运行)–进程是可执行的,正在执行或在运行队列中等待,是进程在用户空间中执行的唯一可能的状态
b、TASK_INTERRUPTIBLE(可中断)–进程正在睡眠(阻塞)
c、TASK_UNINTERRUPTIBLE(不可中断)–该状态中进程对信号不做响应
d、_TASK_TRACDED—被其他进程跟踪的进程(使用ptrace对调试进程进行跟踪)
e、_TASK_STOPPED(停止)—进程停止执行,在收到SIGSTOP,SIGTSTP,SIGTTIN,SIGTTOUT等信号时
## 4、进程家族树
在Linux中进程之间是存在继承关系的,所有的进程都是pid为1的init进程的后代
init进程的启动:内核在系统启动的最后阶段启动init进程,由该进程最终完成系统启动(init进程的描述符是作为init_task静态分配)
系统中进程之间的关系:每个进程必有一个父进程,每个进程也拥有0或多个子进程,有同一个父进程的是兄弟,进程之间的关系存放在task_struct中,在task_struct中都有一个指向其父进程的指针(parent)和children的子进程链表
3、进程创建
Unix中进程创建使用fork()和exec()。
fork拷贝当前进程创建一个子进程,子进程和父进程之间的区别:pid、ppid(父进程的进程号)和特有资源和统计量
exec函数负责读取可执行文件并将其载入地址空间运行
## 1、写时拷贝
Linux的fork使用写时拷贝(copy-on-write)页实现
写时拷贝:是一种可以推迟甚至免除拷贝数据的技术,内核并不会复制整个进程地址空间,而是让父子进程共享同一个拷贝
优点:只有在需要才会数据拷贝,在不需要时是以只读的方式共享,避免拷贝大量不需要的数据
## 2、fork()
Linux通过clone()系统调用实现fork(fork,vfork,_clone是根据不同的参数调用clone),通过参数指定父子进程需要共享的资源,clone调用do_fork()
copy_process()完成的工作:
a、通过dup_task_struct()创建新进程的内核栈、thread_info结构和task_struct,这些和父进程相同
b、检查并确保新创建进程后,用户的进程数不超过资源限制
c、将子进程描述符中的值清0或设为初始值(主要是统计值)
d、通过copy_flags()以更新task_struct中的flags成员,PF_SUPERPRIV(表示是否拥有root权限)被清0,PF_FORKNOEXEC(表示进程还没调用exec)被设置
e、分配新的pid(alloc_pid)
f、根据传递给clone的参数和标志,copy_process()拷贝或共享打开的文件、文件系统信息、信号处理函数、进程地址空间和命名空间等。一般这些资源会被给定进程的所有线程共享
g、返回一个指向子进程的指针
vfork和fork之间的不同:vfork不拷贝父进程的页表
4、线程在Linux中的实现
线程机制:是现代编程技术中常用的抽象概念,提供在同一程序中共享内存地址空间运行的一组线程
Linux中从内核的角度没有线程的概念,而是将线程当作进程来处理的,每个线程都有自己的task_struct,线程是一个可以和其他线程共享资源的特殊进程
特别说明
内核线程:内核线程和普通的进程之间的区别在于内核线程没有独立的地址空间(实际指指向地址空间的mm指针被设置为NULL),只在内核空间运行
通过ps -ef查看内核线程
5、进程终结
进程需要终结并在终结时内核需要回收资源并告知父进程,进程的析构时自身引起的通过调用exit()系统调用
exit()调用do_exit()回收资源,调用release_task()释放进程描述符
特殊点:孤儿进程
孤儿进程:父进程在子进程退出之前退出
保证孤儿进程退出的机制:给子进程所在当前的进程组中找一个进程作为父亲,如果不行就使用init作为父进程
在do_exit中会调用exit_notify调用forget_original_parent执行寻找父进程的过程