Linux学习笔记——进程管理(二)

3、进程创建

fork() 通过拷贝当前进程创建一个子进程,子进程与父进程的区别仅仅在于PID、PPID和某些资源和统计量。exec()函数(指所有的exec()一族的函数)负责读取可执行文件并将其载入地址空间开始运行。

3.1 写时拷贝

Linux的fork()使用写时拷贝(copy-on-write)页实现,创建子进程时,内核并不复制整个进程地址空间,而是让父进程和子进程共享同一个拷贝。只有在需要写入的时候,数据才会复制,从而使各个进程拥有各自的拷贝,在此之前,资源只是以只读的方式共享。例如,fork()后立即调用exec(),就无须复制了。
fork()的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。

3.2 fork()

Linux通过clone()系统调用实现fork(),这个调用通过一系列的参数标志来指明父、子进程需要共享的资源。
fork(),vfork(),__clone()库函数都根据各自需要的参数标志去调用clone(),由clone()去调用do_fork(),由do_fork()调用copy_process()函数,然后让进程开始运行。copy_process()函数完成的工作如下:

图摘自《Linux内核设计与实现(第三版)》

在这里插入图片描述
在这里插入图片描述
回到do_fork()时,如果copy_process()函数成功返回,新创建的子进程被唤醒并让其投入运行。内核有意选择子进程首先执行,因为一般子进程都会马上调用exec()函数,这样可以避免写时拷贝的额外开销,如果父进程首先执行的话,有可能会开始向地址空间写入。

3.3 vfork()

除了不拷贝父进程的页表项外,vfork()系统调用和fork()的功能相同。子进程作为父进程的一个单独的线程在它的地址空间里运行,父进程被阻塞,知道子进程退出或执行exec()。子进程不能向地址空间写入。

4、线程在Linux中的实现

从Linux内核的角度,并没有线程这个概念。Linux把所有的线程都当做进程来实现,线程仅仅被视为一个与其他进程共享某些资源的进程。每个线程都拥有唯一里属于自己的task_struct。

4.1 创建线程

线程的创建和普通进程的创建类似,只不过在调用clone()的时候需要传递一些参数标志来指明需要共享的资源,例:

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

传递给clone()的参数标志决定了新创建进程的行为方式和父子进程之间共享的资源种类,下表列举了这些clone()用到的参数标志以及它们的作用,这些参数标志是在<linux/sched.h>中定义的。

参数标志含义
CLONE_FILES共享文件描述符
CLONE_FS共享文件系统信息
CLONE_IDLETASK将PID设置为0(只供idle进程使用)
CLONE_NEWNS为子进程创建新的命名空间
CLONE_PARENT将子进程的父进程置为调用者的父进程
CLONE_PTRACE继续对子进程进行跟踪
CLONE_UNTRACED防止跟踪进程在子进程上强制执行CLONE_PTRACE
CLONE_SETTID将TID回写至用户空间
CLONE_SETTLS为子进程创建新的TLS
CLONE_SIGHAND共享信号处理函数及被阻断的信号
CLONE_SYSVSEM共享System V信号量的撤销值
CLONE_THREAD将子进程置于父进程的线程组中
CLONE_VFORK调用vfork(),挂起父进程直至子进程退出或者调用exec()
CLONE_STOP以TASK_STOPPED状态开始进程
CLONE_SETTLS为子进程创建新的TLS(thread-local storage)
CLONE_PARENT_SETTID设置父进程的TID
CLONE_CHILD_SETTID设置子进程的TID
CLONE_CHILD_CLEARTID清除子进程的TID
CLONE_VM共享地址空间(虚拟内存)

4.2 内核线程

内核经常需要在后台执行一些操作,这种任务可以通过内核线程来完成。内核线程是独立运行在内核空间的标准进程,其与普通的进程之间的区别在于内核线程没有独立的地址空间(实际上指向地址空间的mm指针被设置为NULL),它们只在内核空间运行,从来不切换到用户空间去。内核进程和普通的进程一样,可以被调度,也可以被抢占。
内核线程启动后就一直运行,直到带哦用do_exit()退出,或者内核的其他部分调用kthread_stop()退出。

5、进程终结

一般来说,进程的析构是自身引起的,它发生在进程调用exit()系统调用时(显式或隐式地)。当进程接收到它既不能处理也不能忽略的信号或异常时,它哈有可能被动地终结。其大部分任务都要靠do_exit()(定义于kernel/exit.c)来完成。
在调用了do_exit() 之后,与进程相关联的所有资源被释放掉,进程不可运行,处于EXIT_ZOMBIE退出状态。它占用的所有内存就是内核栈、thread_info和task_struct结构。此时进程存在的唯一目的就是向它的父进程提供信息。

5.1 删除进程描述符

进程终结时所需的清理工作和进程描述符的删除被分开执行。在父进程获得已终结的子进程的信息后,或者通知内核它并不关注那些信息后,子进程的task_struct结构才被释放。
wait() 这一族函数都是通过唯一的一个系统调用wait4() 来实现的,它的标准动作是挂起调用它的进程,直到其中的一个子进程退出,此时函数会返回该子进程的PID
当最终需要释放进程描述符时,release_tast() 会被调用,完成一系列工作,最终将进程描述符和所有进程独享的资源全部释放掉。

5.2 孤儿进程的处理

如果父进程在子进程之前退出,必须有机制来保证子进程能找到一个新的父亲,否则这些成为孤儿的进程就会在退出时永远处于僵死状态,白白耗费内存。解决办法是给子进程在当前线程组内找一个线程作为父亲,如果不行,就让init做它们的父进程。
给子进程找到合适的养父进程之后,只需要遍历所有的子进程并为它们设置新的父进程。
同样需要给ptraced的子进程寻找父亲,通过遍历两个单独的链表子进程链表ptrace子进程链表,搜索相关的兄弟进程,用两个相对较小的链表减轻遍历带来的损耗
init进程会例行调用wait()来检查其子进程,清除所有与其相关的僵死进程。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值