进程
进程
- 什么是进程
处于执行期间的程序
- 进程资源
通常包括的资源:打开的文件,挂起的信号,内核内部数据,处理器状态,一个或多个具有
内存映射的内存地址空间及一个或多个执行线程。存放全局变了的数据段等。
进程创建
-
调用fork,在该调用结束时,在返回点相同位置,父进程恢复执行,子进程开始执行。
fork调用一次,从内核返回两次。 -
fork实际由clone()系统调用实现
-
程序通过调用exit()系统调用退出,这个函数会终结进程并将其占有的资源释放
进程描述符及任务结构
进程在内核进程也叫task
内核把进程的列表存放到叫做任务队列task list的双向循环链表中。链表的每一项
都是task_struct,成为进程描述符的结构。
进程描述符中包含的数据可以完整的描述一个正在执行的进程:他打开的文件,进程地址空间
,挂起的信号,进程的状态还有其他信息
进程队列
进程描述符和内核栈
每个thread_info结构在他的内核栈尾端分配,结构中的task域中存放当前任务实际的task_struct指针
进程状态
进程描述符中的state描述进程的当前状态,每个进程的必然处于五种状态之一
task_running
task_interruptible
task_uninterruptible
__task_traced
__task_stopped
设置某个进程的状态。set_task_state(task,state);
进程上下文
程序在用户空间执行,当执行系统调用或触发一次,就陷入内核,我们称为内核代表
进程执行并处于进程上下文中。
进程家族树
task_struct 中包含父进程指针parent和指向子进程链表的children
创建过程
fork()和exec()函数,首先fork通过拷贝当前进程创建一个子进程。子进程与父进程的区别
仅仅在于pid,ppid和某些资源和统计量(挂起的信号)
linux通过clone系统调用实现fork(),这个调用通过一系列的参数标志来指明父子进程
需要共享的资源
do_fork完成了创建中的大部分工作,该函数调用copy_process函数,让程序开始运行
copy_process函数流程
- dup_task_struct 创建一个内核栈,thread_info结构和task_struct,这些值与当前进程
完全相同。
linux通过clone系统调用实现fork(),这个调用通过一系列的参数标志来指明父子进程
需要共享的资源
do_fork完成了创建中的大部分工作,该函数调用copy_process函数,让程序开始运行
copy_process函数流程
dup_task_struct 创建一个内核栈,thread_info结构和task_struct,这些值与当前进程
完全相同。
linux通过clone系统调用实现fork(),这个调用通过一系列的参数标志来指明父子进程
需要共享的资源
do_fork完成了创建中的大部分工作,该函数调用copy_process函数,让程序开始运行
copy_process函数流程
- dup_task_struct 创建一个内核栈,thread_info结构和task_struct,这些值与当前进程
完全相同。 - 检查并确保当前用户所有的进程数没有超出限制
- 子进程使自己与父进程区别开,进程描述符中的许多成员要被清0或初始值
- 子进程状态被设置为task_uninterruptible,保证不会被投入运行
- 调用copy_flags以更新task_struct的flags成员。表明用户是否拥有超级权限的
PF_SUPERPRIV标志被清0,表明进程还没有调用exec函数的PF_FORKNOEXEC标志被设置 - 调用alloc_pid 为信进程分配一个有效pid
- 根据clone参数标志,拷贝或共享打开的文件,文件系统信息,信号处理函数,进程地址空间和命名空间等
- 最后做扫尾工作并返回一个指向子进程的指针
- 返回到do_fork,如果copy_process 返回成功,新创建的子进程被唤醒并让其投入运行
线程
- 什么是线程
是在进程中的活动的对象
- 线程资源
拥有独立的程序计数器,进程栈和一组寄存器。内核调度的基本单位。
线程在linux 中的实现
从内核角度来说没有线程概念,linux把线程当作进程来实现,线程仅仅被视为一个与其他进程共享某些资源的进程
每个线程拥有自己的task_struct。
创建线程
线程创建和普通进程创建类似,只不过是调用clone()的时候传递一些参数标志来指明需要共享的资源
clone(clone_vm | clone_fs | clone_files | clone_sighand,0)
内核线程和普通进程间的区别在于内核线程没有独立的地址空间,只在内核空间运行,从来不切换到用户空间.
struct task_struct *kthread_create(int (*threadfn)(void data),voiddata,const char namefmt[])
调用ktread_run运行
进程终结
调用exit()
将task_struct 中的标志设置为PF_EXITING
调用del_timer_sync删除任一内核定时器
调用exit_mm()释放进程占有的mm_struct
调用sem__exit(),如果进程等候IPC信号,他则离开队列
调用exit_files 和exit_fs(),递减文件描述符,文件系统数据的引用计数,如果某个
引用计数为0,就代表没有进程使用,此时可以释放。
把task_struct的exit_code设置为exit()函数提供的值
调用exit_notify()向父进程发送信号,给子进程重新找养父。
do_exit()调用chedule()切换到新的进程。do_exit()永不返回。
进程相关联的资源全部释放,进程不可运行并处于exit_zombie退出状态。thread_info和task_struct结构唯一母的是向父进程提供信息。父进程检索信息后,通知内核是无用信息,由进程所持有的剩余资源被释放。
孤儿进程
父进程在子进程之前退出,必须有机制来保证子进程找到一个新的父亲。否则这些成为孤儿的进程就会永远处于僵死状态。
do_exit调用exit_notify()
调用forget_original_parent()
调用find_new_reaper来寻父进程(进程组内的其他进程,如果找不到就找init进程)
调用ptrace_exit_finsish(),进行寻父过程,不过这次是给ptrace的子进程寻找父亲
init进程会例行调用wait()来检查其子进程,清除所有与其相关的僵尸进程