Linux进程的创建与撤销

内核中进程的信息

进程的信息主要保存在task_struct中
进程标识PID和线程标识TID对于同一个进程或线程来说都是相等的。

ps -eo pid,tid,ppid,comm

进程的生命周期

在这里插入图片描述

进程的创建

Linux中创建进程与其他系统有个主要区别,
Linux中创建进程分2步:fork()exec()

  1. fork: 通过拷贝当前进程创建一个子进程
  2. exec: 读取可执行文件,将其载入到内存中运行

创建的流程

  1. 调用dup_task_struct()为新进程分配内核栈,task_struct等,其中的内容与父进程相同。
  2. check新进程(进程数目是否超出上限等)
  3. 清理新进程的信息(比如PID置0等),使之与父进程区别开。
  4. 新进程状态置为 TASK_UNINTERRUPTIBLE
  5. 更新task_struct的flags成员。
  6. 调用alloc_pid()为新进程分配一个有效的PID
  7. 根据clone()的参数标志,拷贝或共享相应的信息
  8. 做一些扫尾工作并返回新进程指针

创建进程函数fork()实际上只是调用clone()函数
创建线程和进程的步骤一样,只是最终传给clone()函数的参数不同
通过一个普通的fork来创建进程,相当于:clone(SIGCHLD, 0)创建一个和父进程共享地址空间文件系统资源文件描述符信号处理程序的进程,即一个线程
clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0)在内核中创建的内核线程与普通的进程之间还有个主要区别在于:内核线程没有独立的地址空间,它们只能在内核空间运行

clone()函数的定义

进程的终止

和创建进程一样,终结一个进程有很多步骤:

子进程上的操作(do_exit)

  1. 设置task_struct中的标识成员设置为PF_EXITING
  2. 调用del_timer_sync()删除内核定时器, 确保没有定时器在排队和运行
  3. 调用exit_mm()释放进程占用的mm_struct
  4. 调用sem__exit(),使进程离开等待IPC信号的队列
  5. 调用exit_files()和exit_fs(),释放进程占用的文件描述符和文件系统资源
  6. 把task_struct的exit_code设置为进程的返回值
  7. 调用exit_notify()向父进程发送信号,并把自己的状态设为EXIT_ZOMBIE
  8. 切换到新进程继续执行

子进程进入EXIT_ZOMBIE之后,虽然永远不会被调度,关联的资源也释放掉了,但是它本身占用的内存还没有释放,比如创建时分配的内核栈,task_struct结构等。这些由父进程来释放。

父进程上的操作

父进程受到子进程发送的exit_notify()信号后,将该子进程的进程描述符和所有进程独享的资源全部删除。
从上面的步骤可以看出,必须要确保每个子进程都有父进程,如果父进程在子进程结束之前就已经结束了会怎么样呢?子进程在调用exit_notify()时已经考虑到了这点。
如果子进程的父进程已经退出了,那么子进程在退出时,exit_notify()函数会先调用forget_original_parent(),然后再调用find_new_reaper()来寻找新的父进程。
find_new_reaper()函数先在当前线程组中找一个线程作为父亲,如果找不到,就让init做父进程。(init进程是在linux启动时就一直存在的)

猜你喜欢

转载自blog.csdn.net/weixin_44029810/article/details/107440123