操作系统进程线程(二)—父子进程、僵尸进程、孤儿进程、进程终止、守护进程

父子进程、进程组、作业、会话

父进程

已创建一个或者多个进程

子进程

fork创建的。这个函数被调用一次但是返回两次,子进程返回0,父进程返回子进程id。

fork之后,操作系统会赋值一个与父进程完全相同的子进程,虽然是父子关系,但是更像是兄弟关系。这两个进程共享代码,但是数据空间是互相独立的,数据空间、指令、指针完全相同,子进程拥有当前父进程运行到的位置(PC相同)。

子进程拥有父进程的资源

进程的资格(真实(real)/有效(effective)/已保存(saved)用户号(UIDs)和组号(GIDs))、环境、堆栈、内存、进程组号。

子进程独有

进程号、不同的父进程号(译者注:即子进程的父进程号与父进程的父进程号不同, 父进程号可由getppid函数得到);资源使用(resource utilizations)设定为0

进程组

多个进程的集合,其中有一个组长,PID等于进程组的PGID,只要这个组里面有一个进程存在那么这个组就存在,与组长进程是否终止无关

作业

shell分前后台来控制的不是进程而是作业或者进程组。
一个前台作业由多个进程组成,一个后台也由多个进程组成。shell可以运行一个前台作业和任意多个后台作业。这就是作业控制

为什么只能运行一个前台作业?
前台作业是指当前终端窗口中运行的作业,而一个后台作业则是在后台运行的作业,不会占用当前终端窗口。
一个终端窗口只能有一个前台作业,是因为终端窗口只能和一个进程交互,用户在终端窗口输入命令,终端窗口会将命令传递给前台作业,如果有多个作业同时处于前台,那么输入的命令将被发送给哪个作业时不确定的,这会导致混乱和错误。

作业与进程组的区别

如果作业中有某个进程创建了子进程,则这个子进程是不属于该作业的,以但作业运行结束,shell就把自己提到前台(子进程还在,但是子进程不属于作业)。如果原来的前台进程还存在(这个子进程还没有终止),他将自动变为后台进程组。

当我们在前台新起了一个作业,shell就被提到了后台,因此shell就没有办法再继续接受我们的指令并且解析运行了。 但是如果前台进程退出了,shell就会有被提到前台来,就可以继续接受我们的命令并且解析运行。

会话

是一个躲着多个进程组的集合。一个会话有一个控制终端,在xshell或者winscp中打开一个窗口就是新建一个会话。

孤儿进程

如果父进程退出,子进程还没有退出,那些子进程将成为孤儿进程。孤儿进程被1号进程init进程收养。那么子进程的父进程将变成init进程。由init进程对他们完成状态收集工作。

僵尸进程

在操作系统中,当一个进程终止时,它的状态信息仍然被保留在系统中,直到其父进程调用wait或waitpid等系统调用来获取其终止状态信息。如果父进程没有及时调用这些系统调用来获取终止状态信息,那么这个已经终止的子进程就会成为一个僵尸进程(Zombie Process)。
僵尸进程占用了系统资源,因为它们仍然占用了进程ID、进程表项和一些其他资源,而这些资源可能需要被其他进程使用。因此,需要及时清除僵尸进程,以释放这些资源。

Linux中,使用ps aux查看信息,发现僵尸进程状态为“Z”

当一个进程终止时,如果它的子进程仍然处于僵尸状态,那么这些子进程的父进程ID将被重置为1,也就是init进程。此时,init进程会调用wait或waitpid等系统调用来获取这些子进程的终止状态信息,并清除它们的僵尸状态,从而释放系统资源。

需要注意的是,当父进程终止时,操作系统会将其所有未处理的信号发送给其子进程。如果子进程没有捕获这些信号,那么它们将被终止,成为僵尸进程。因此,在编写程序时,需要及时处理信号,以避免出现僵尸进程。

避免僵尸进程

  • 通过signal(SIGCHID,SIG_IGN)通知内核对子进程结束不关心,由内核回收。如果想让父进程挂起,可以在父进程中加入条语句signal(SIGCHLD,SIG_IGN),表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。(可以让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用资源)
  • 父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
  • 如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
  • 通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
  • 使用双向管道:父进程在fork子进程之前创建一个管道,并在子进程中关闭读端口,父进程关闭写端口,当子进程终止时,会向管道写入一个消息,父进程在读取到消息后,使用wait或waitpid等系统调用获取子进程的状态信息,从而避免出现僵尸进程。

进程终止

进程终止的几种方式

  • main函数自然返回
  • exit,属于C函数库
  • _exit,属于系统调用
  • 调用abort函数,异常程序终止,同时发送SIGABRT信号给调用进程
  • 接收能导致进程终止的信号 ctrl+c,SIGINT

exit与_exit

图片来源阿秀的学习笔记
在这里插入图片描述

终端退出,终端对应运行的进程会怎样

终端退出的时候会发送SIGHUP给对应的bash进程,bash进程收到这个信号后首先将它发给session下面的进程,如果程序没有对SIGHUP信号做特殊处理,那么进程就会随着终端关闭而退出。

如何回收线程

等待线程结束

int pthread_join(pthread_t tid, void** retval);
主线程调用,等待子线程退出并且回收资源

结束进程

void pthread_exit(void *retval);
子线程执行,用来结束当前线程并通过retval传递返回值,改返回值通过pthread_join获得

分离线程

int pthread_detach(pthread_t tid);
主线程和子线程都可以调用,调用之后和主线程分离,子线程结束的时候自己立即回收资源

如何让进程后台运行

  1. 命令后面加上&,这样是将命令放入到一个作业队列中
  2. ctrl+z挂起进程,使用jobs查看序号,再使用bg%序号后台运行进程
  3. nohup+&,将标准输出和标准错误缺省会被重定向到nohup文件中,忽略所有SIGHUP挂断信号
  4. 运行指令前面加上 setsid,使其父进程编程init进程,不受HUP信号影响
  5. 将命令+&放在()括号中,也可以是进程不受HUP信号影响

守护进程

指在后台运行的,没有控制终端与之相连的进程。它独立于控制终端,周期性的执行某种任务。Linux大多数服务器就是用守护进程的方式实现的,比如WEB服务器。

创建守护进程

  1. 让程序在后台执行:调用fork()产生一个子进程,然后使父进程退出
  2. 调用setsid()创建一个新对话期。控制终端、登录会话和进程组通常是从父进程进程下来的,守护进程要摆脱他们,不受他们的影响,方法是调用setsid()使进程成为一个会话组长。setsid()调用成功之后,进程成为新的会话组长和进程组长,并与原来的登录会话、进程组和控制终端脱离。
  3. 禁止进程重新打开控制终端。这时进程已经成为一个无终端的会话组长,但是它可以重新申请打开一个终端。为了避免这种情况,可以通过使进程不再是会话组长来实现。再一次通过fork创建新的子进程,使调用fork的进程退出。
  4. 关闭不再需要的文件描述符。子进程从父进程继承打开的文件描述符,如果不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误,首先获得最高文件描述符值,然后用一个循环程序,关闭0到最高文件描述符值的所有文件描述符。
  5. 将当前目录更改为根目录
  6. 子进程从父进程继承的文件创建屏蔽字可能会拒绝某些许可权。为防止这一点,使用unmask(0)将屏蔽字清零。
  7. 处理SIGCHLD信号,对于服务器进程,在请求到来时往往生成子进程处理请求。如果子进程等待父进程捕获状态,则子进程将成为僵尸进程,从而占用系统资源。如果父进程等待子进程结束,将增加父进程负担。所以将SIGCHLD信号的操作设置为SIG_IGN,子进程结束的时候就不会产生僵尸进程。
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值