《UNUX环境高级编程》(8)进程控制

1、引言

2、进程标识

  • 每个进程都用一个唯一的非负整数标识,即为进程id:pid。进程ID是可以复用的,当一个进程终止时,其进程ID就可以用来标识其他进程。
  • 系统中有一些专用进程:
    • 进程ID为0的是调度进程,也称交换进程(swapper),它并不执行任何磁盘上的程序,它是内核中的系统进程;
    • 进程ID为1的是init进程,此进程负责在自举内核后启动一个UNIX系统,init进程绝不会终止,它是一个以超级用户特权运行的普通用户进程;init通常读取与系统有关的初始化文件(/etc/rc*文件或/etc/inittab文件,以及在/etc/init.d中的文件)并将系统引导到一个状态(如多用户)。此外,init是所有孤儿进程的父进程。
    • 进程ID为2的是页守护进程,负责支持虚拟存储器系统的分页操作。
  • 通过以下函数获取一个进程的pid等id信息:
    pid_t getpid(void);  // 调用进程pid
    pid_t getppid(void); // 调用进程的父进程pid
    uid_t getuid(void);  // 调用进程的实际用户id
    uid_t geteuid(void); // 调用进程的有效用户id
    gid_t getgid(void);  // 调用进程的实际组id
    gid_t getegid(void); //调用进程的有效组id
    
    注意:这些函数都没有出错返回。

3、函数fork

  • 一个现有进程可以通过调用fork复刻一个新进程

    pid_t fork(void);
    
    • fork函数调用一次返回两次。对于子进程返回值是0;对于父进程返回值是子进程pid。子进程只能有一个父进程,通过getppid获得父进程pid。
    • 子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本,子进程获得父进程的数据空间、堆和栈的副本。注意这是子进程所拥有的副本,父子进程并不共享这些存储空间部分。父进程和子进程共享正文段.text。
    • 由于fork之后进程跟着exec,所以现在很多fork实现并不执行父进程数据段、堆和栈的完全副本,而是使用写时复制(copy-on-write)这些区域由父子进程共享,并且内核将它们的访问权限修改为只读。如果父子进程中的一个试图修改这些区域,则内核只为修改区域的那块内存制作副本。
  • 实例:演示了一个fork函数,可以看出子进程对变量所做的改变并不影响父进程中该变量的值

    #include "apue.h"int		globvar = 6;		/* external variable in initialized data */
    char	buf[] = "a write to stdout\n";int
    main(void)
    {int		var;		/* automatic variable on the stack */pid_t	pid;var = 88;/*注意sizeof和strlen的区别:1)strlen需要进行一次系统调用,它计算的是不包含终止null字节的字符串长度。2)sizeof计算包含null字节的字符串长度,sizeof是在编译时计算缓冲区长度,也就是说它不需要进行系统调用。*/if (write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1) /*write函数是不带缓冲的,其数据写道标准输出一次*/err_sys("write error");/*printf属于标准I/O库,是带缓冲的。如果标准输出连接到终端设备,则它是行缓冲的,否则它是全缓冲的。这一点会影响到冲洗数据的时机。*/printf("before fork\n");	/* we don't flush stdout */if ((pid = fork()) < 0) {err_sys("fork error");} else if (pid == 0) {		/* fork函数调用一次返回两次,若返回的是0,则为子进程 */globvar++;				/* 子进程对数据进行更改,通过命令行可以发现子进程的变量值改变了,而父进程没有 */var++;} else { /* fork函数调用一次返回两次,若返回的是子进程的PID,结果>0,为父进程 */sleep(2);/* 父进程是自己睡眠2s,目的是让子进程先执行,但并不能保证2s是足够的 */}printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,var);exit(0);
    }
    

    命令行输出结果:

    lh@LH_LINUX:~/桌面/apue.3e/proc$ ./fork1
    a write to stdout
    before fork
    pid = 3638, glob = 7, var = 89
    pid = 3637, glob = 6, var = 88
    lh@LH_LINUX:~/桌面/apue.3e/proc$ ./fork1 > result.txt
    lh@LH_LINUX:~/桌面/apue.3e/proc$ cat result.txt 
    a write to stdout
    before fork
    pid = 3650, glob = 7, var = 89
    before fork
    pid = 3649, glob = 6, var = 88
    
    • fork之后是父进程先执行还是子进程先执行是不确定的,取决于内核的调度算法。如果要求父进程和子进程之间相互同步,则要求某种形式的进程间通信。
    • 根据命令行输出结果我们可以发现:
      • 当以交互方式运行程序时,标准输出连接至终端设备,此时为行缓冲,标准输出缓冲区由换行符\n冲洗,最后只得到printf输出的行一次。
      • 当将标准输出重定向到一个文件时,此时为全缓冲,当调用fork后,该行数据仍在缓冲区,然后在将父进程数据空间复制到子进程中时,该缓冲区数据也被复制到子进程中,此时父进程和子进程各自有了带该行内容的缓冲区。后面的语句printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globvar,var);将数据追加到已有的缓冲区中。当每个进程终止时(exit()会执行标准I/O清理程序),其缓冲区的内容都被写到相应文件中。
  • 文件共享

    • 父进程的所有打开文件描述符都被复制到子进程中,就好像执行了dup函数。父进程和子进程每个相同的打开描述符共享一个文件表项。如下图所示。
      在这里插入图片描述
    • 可以看出,fork之后的父子进程每个相同的打开描述符共享一个文件表项,因此也共享相同的文件偏移量(读写指针)
    • 除了打开文件之外,父进程的很多其他属性也由子进程继承
      • 实际用户ID、实际组ID、有效用户ID、有效组ID
      • 附属组ID
      • 进程组ID
      • 会话ID
      • 控制终端
      • 设置用户ID标志和设置组ID标志
      • 当前工作目录
      • 根目录
      • 文件模式创建屏蔽字
      • 信号屏蔽和安排
      • 对任一打开文件描述符的执行时关闭标志(close-on-exec)
      • 环境
      • 连接的共享存储段
      • 存储映像
      • 资源限制
    • 父子进程的区别
      • fork返回值不同
      • 进程ID不同
      • 子进程的tms_utimetms_stimetms_cutimetms_ustime值设置为0
      • 子进程不继承父进程设置的文件锁。
      • 子进程的未处理闹钟被清除。
      • 子进程的未处理信号集设置为空集。
  • fork失败原因

    • 系统中已经有了太多的进程
    • 该实际用户ID的进程总数超过了系统限制
  • fork常用以下方法

    • 一个父进程希望复刻自己,使父进程和子进程同时执行不同的代码段。在网络服务器中较为常见:父进程等待客户端的服务请求,请求到达时fork使子进程处理此请求,父进程则继续等待下一个服务请求。
    • 一个进程要执行一个不同的程序,shell常使用这种方式。子进程从fork返回后立即调用exec。有些操作系统将fork之后立刻exec组合成一个操作:spawn,但是UNIX将这两个操作分开,因为子进程可以在fork和exec之间更改自己的属性。

4、函数vfork(不推荐使用)

  • vfork函数调用方式和fork相同,但语义不同。现在不应该使用这个函数
  • vfork用于创建一个新进程,而该新进程的目的就是exec一个新程序。但是它不会将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或exit)。不过在子进程exec或exit之前,它在父进程的空间中运行。这种方式提高了效率,但是如果子进程修改了数据(子进程修改数据会影响到父进程,因为共享存储空间)、进行函数调用、或者没有调用exec或exit就返回可能会带来未知结果。
  • 并且vfork保证子进程先运行,在子进程调用exec或exit之后父进程才可能被调度运行。即子进程调用这两个函数中的任意一个时,父进程会恢复运行。(如果在调用这两个函数之前子进程依赖于父进程的进一步操作,会导致死锁)。
  • 实例:vfork函数的使用
    int main(int argc, char* argv[]) {int num = 1;if(vfork() == 0) { // 子进程cout << "子进程执行" << endl;num ++;cout << "子进程终止" << endl;_exit(0);} else { // 父进程cout << "父进程执行" << endl;cout << "num : " << num << endl;cout << "父进程终止" << endl;}
    }
    
    命令行输出
    $ ./a.out 
    > 子进程执行
    > 子进程终止
    > 父进程执行
    > num : 2
    > 父进程终止
    
    可以看出子进程修改变量值影响了父进程中该变量,这是因为父子进程共享存储空间。

5、函数exit

  • 进程有5种正常终止和3种异常终止。其中正常终止:

    • main函数内执行return语句,等效于调用exit
    • 调用exit函数,此操作调用终止处理程序(atexit登记的函数),关闭所有标准I/O流,然后调用_exit
    • 调用_exit或_Exit。其操作提供一种无需运行终止处理程序或信号处理程序而终止的方法。(exit是标准C库中的一个函数,_exit是一个系统调用)
    • 进程的最后一个线程在启动例程中执行return语句。但是,该线程返回值不用做进程的返回值。当最后一个线程从其启动例程返回时,该进程以终止状态0返回
    • 进程的最后一个线程调用pthread_exit函数。此时进程终止状态总是0,这与传给pthread_exit参数无关
  • 3种异常终止:

    • 调用abort。它产生SIGABRT,这是下一种异常终止的特例
    • 当进程接到某种信号时。信号可由进程自身(如调用abort函数)、其他进程或内核产生。
    • 最后一个线程对“取消”请求做出响应。默认情况下“取消”以延迟方式发生:一个线程要求取消另一个线程,若干时间之后,目标线程终止。
  • 不管进程是以何种方式终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开标识符,释放它所使用的存储器等。

  • 希望子进程能够通知其父进程它是如何终止的。可以通过将退出状态作为参数传递给三个终止函数(exit、_exit、_Exit)。如果是异常终止,则不再由这三个函数的参数决定其终止状态,而是内核(不是进程本身)产生一个表明其异常终止原因的终止状态。 在任何情况下,父进程都能够通过wait或waitpid取得其终止状态。

  • 退出状态和终止状态区别:

    • 退出状态是传递给三个终止函数的参数,或main的返回值。
    • 在调用_exit时,内核将退出状态转换成终止状态。
  • 如果父进程在子进程之前终止,那么这些子进程的父进程会改变为init进程:当一个进程终止时,内核逐个检查所有活动进程,以判断他是否是正要终止进程的子进程,如果是,则该进程的父进程ID改为1,保证了每个进程都有一个父进程。

  • 内核为每个终止子进程保存了一定量的信息,父进程调用wait或waitpid时可以得到这些信息(至少包括进程ID、该进程的终止状态、该进程使用的CPU时间总量)。一个已经终止,但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息并释放它仍占用的资源)的进程被称为僵尸进程。 即没有被父进程wait的终止子进程都是僵尸进程。

  • 一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个等信息。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。

  • 被init进程收养的进程终止不会变成僵尸进程:只要有一个进程终止,init进程就会调用一个wait函数取得其终止状态,防止了系统中塞满僵尸进程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/18302.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

lua 请求ftp服务器数据,下载文件

1、装入ftp库 2、调用ftp的get()方法 3、get()方法参数格式&#xff1a; 4、将返回到的数据写入文件中 例如&#xff0c;本次获取专利数据系统 http://patdata1.cnipa.gov.cn/ 的ftp站点数据 local ftp require("socket.ftp")--此处我没填端口号 file,err ftp.g…

postgrep 9.4 断电后启动不了

journalctl -xe1、问题1&#xff1a;pg_ctl: another server might be running pg_ctl: another server might be running 解决方法&#xff1a;删除原来没有删除的pid文件 rm /opt/PostgreSQL/9.4/data/postmaster.pid 2、问题2 postgres文件丢失 - Unit postgresql-9.4.ser…

使用echarts+echarts-gl绘制3d地图,实现地图轮播效果

记录一下大屏开发中使用到的echarts-gl大屏的页面根据需求前前后后改了几个版本了&#xff0c;地图的样式也改了又改这里记录一下&#xff0c;因为echarts属性用到的比较多也比较杂&#xff0c;防止以后需要用到忘记了 目录 初始效果 效果图&#xff1a; 适应大屏风格的发光…

Centos7离线模式安装Redis6.2.13详细步骤(rpm方式)

本篇文章主要介绍在CentOS7服务器中安装Redis6.2.13&#xff0c;前提是需要有gcc的环境&#xff0c;那么在此我也会向大家介绍gcc的详细安装过程&#xff0c;参考了很多其它相关博客&#xff0c;但有些博主的文章可能是搬运的&#xff0c;导致我在实操时出现报错&#xff0c;那…

机器学习28:《推荐系统-I》概述

在互联网领域&#xff0c;推荐系统&#xff08;Recommendation Systems&#xff09;的应用非常广泛。在音视频方面&#xff0c;如抖音、快手、哔哩等&#xff1b;在电商平台方面&#xff0c;如京东、淘宝、拼多多等。推荐有助于帮助用户快速发现潜在感兴趣的内容&#xff08;音…

设计模式之二:观察者模式

假定我们需要为Weather-O-Rama公司建立一个气象站系统&#xff0c;除已有的WeatherData有数据源类&#xff0c;还需要更新三个布告板的显示&#xff1a;目前状况&#xff08;温度、湿度、气压&#xff09;、气象统计和天气预报。 1 以下是一个可能的实现 class WeatherData { …

C++类相关概念

1. 函数形参默认值 &#xff08;1&#xff09; 建议函数&#xff08;不仅仅是构造函数&#xff09;形参默认值只在函数声明中指定&#xff1b; &#xff08;函数声明和定义写在同一个文件中&#xff0c;则函数声明、定义两者之一或两者都可指定形参默认值&#xff0c;两者都指…

云原生监控——VictoriaMetrics

1.简介 VictoriaMetrics是一个快速高效且可扩展的监控解决方案和时序数据库&#xff0c;可以作为Prometheus的长期远端存储&#xff0c;具备的特性有&#xff1a; 支持prometheus查询api&#xff0c;同时实现了一个metricsql 查询语言支持全局查询视图&#xff0c;支持多prom…

在OK3588的Ubuntu系统上安装Firefox浏览器

文章目录 概要配置上网环境安装的具体命令 概要 因为Ubuntu系统里面没有安装浏览器&#xff0c;为了方便使用&#xff0c;提高工作效率&#xff0c;我们安装一下Firefox浏览器。 Firefox是一款适用于Ubuntu系统的免费和开源的Web浏览器。由Mozilla Foundation和其子公司Mozil…

生成图片验证码-Google Kaptcha

CaptchaImage生成 验证码 图片 captchaProducerMath.createText() 类似 captchaProducer.createText() 混合带字符的char如下 从若依学的&#xff0c;先看他的引用方式 package com.ruoyi.web.controller.common;import java.awt.image.BufferedImage; import java.io.IOExcept…

【数据仓库】BI看板DataEase入坑指南

开头夸夸国产开源BI软件DataEase&#xff0c;支持常见各种报表&#xff0c;还支持图表联动和上下级钻取&#xff0c;超赞有木有&#xff01;&#xff01;&#xff01; 再来为什么说入坑&#xff0c;源码启动各种不服啊。本地用的maven3.5一直导入不了Java项目backend。后来看了…

React-Native学习,RN的容器Flex-Box布局

justify-content&#xff08;在RN中属性名称为&#xff1a;justifyContent&#xff09;在主轴上对齐方式 align-items&#xff08;在RN中属性名称为&#xff1a;alignItems&#xff09;在交叉轴上的对齐方式 在React Native中&#xff0c;当没有设置容器的主轴方向时&#xf…