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

news2024/10/7 18:28:24

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.coloradmin.cn/o/743687.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软件测试】Git 远程仓库的使用(详细)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 查看远程仓库 想…

Docker笔记 容器的数据卷

1. 数据卷概念 思考&#xff1a; Docker容器删除后&#xff0c;在容器中产生的数据还在吗&#xff1f; 答案是不在了&#xff0c;数据存放在容器中&#xff0c;如果将容器删除&#xff0c;数据也会被一并删除 Docker容器和外部机器可以直接交换文件吗&#xff1f; 答案是不…

为什么需要Promises ?

同步"异步操作", 避免Block多层嵌套造成的"回调地狱" The problem with async code (without Promises) Typically, async operations take a completion handler in a form of a block, which is called to provide either a result or an error. To per…

银河麒麟服务器v10 sp1 部署 redis 及redis gui 客户端工具

上一篇&#xff1a;银河麒麟服务器v10 sp1 redis开机自动启动_csdn_aspnet的博客-CSDN博客 本文介绍另一种redis安装方式及客户端工具安装。 Redis 是一种内存数据模型存储&#xff0c;可用作数据库、缓冲区和消息传递中继。它是开源的&#xff08;BSD 许可&#xff09;。字符…

热爱python的第一天:初识python,搭建python环境,运行第一个python小程序

目录 1 初始python python 概述 python的应用领域 应用python的公司 2 搭建python 开发环境 2.1 安装python&#xff08;以windows 系统为例&#xff09;&#xff08;1&#xff09;下载安装包 &#xff08;2&#xff09; 下载保存后打开文件夹点击以管理员身份运行 &…

IntegrityError: FOREIGN KEY constraint failed解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

nginx漏洞修复之检测到目标URL存在http host头攻击漏洞

漏洞说明 为了方便的获得网站域名&#xff0c;开发人员一般依赖于HTTP Host header。例如&#xff0c;在php里用_SERVER[“HTTP_HOST”]。但是这个header是不可信赖的&#xff0c;如果应用程序没有对host header值进行处理&#xff0c;就有可能造成恶意代码的传入。 解决方法…

安全狗亮相2023第二届上海网络安全博览会

7月5日至7日&#xff0c;“新耀东方-2023第二届上海网络安全博览会暨高峰论坛”在上海顺利举办。此次大会由上海市信息网络安全管理协会、国家计算机网络应急技术处理协调中心上海分中心、(ISC)2上海分会、上海市普陀区科学技术委员会、上海市网络安全产业示范园共同主办。 作…

Keil5开发STM32F4

Gitee keil工程 软件下载和安装&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1PWDAU0EhVZ6J8h6xH_uw5g 提取码&#xff1a;b343 MDK526.exe Keil.STM32F4xx_DFP.2.17.1.pack keygen_2032.exe JLink_Windows_V640.exe en.stsw-stm32065_v1-7-1_STM32F4 &…

【数据仓库】Windows源码安装DataEase,DataEase二次开发

上文记录了DataEase入门使用指南&#xff0c;本文主要记录Windows下源码安装及二次开发步骤【数据仓库】BI看板DataEase入坑指南_wenchun001的博客-CSDN博客 改动文件 源码 GitHub release 链接: Releases dataease/dataease GitHub SDK 软件环境 后端&#xff1a; JDK …

MongoDB快速入门

虽说现在关系型数据库还是主流&#xff0c;但是面对某些需求的时候&#xff0c;需要非关系型数据库来补充它&#xff0c;学习一个主流的NoSQL数据库还是很有必要的。MongoDB是一个功能丰富的NoSQL数据库&#xff0c;本文整理了它最常用的部分形成了这篇入门教程&#xff0c;希望…

设计模式——命令模式

命令模式 定义 将一个请求封装成一个对象&#xff0c;从而让你使用不同的请求吧客户端参数化&#xff0c;对请求排队或者记录请求日志&#xff0c;可以提供命令的撤销和恢复功能。 命令模式是一个高内聚的模式。 优缺点、应用场景 优点 类间解耦。调用者与接收者之间没有任…

探索AI领域,AI图像安全技术助力行业健康发展

目录 一、AI时代降临二、AIOCR与传统OCR技术三、通过人工智能模型生成AI图片技术探索四、提前布局&#xff0c;合合信息AI图像安全技术助力行业健康发展1、识别医疗门诊发票和报告2、图像篡改检测升级&#xff0c;截图篡改检测3、AIGC判别&#xff0c;人脸伪造检测4、OCR对抗攻…

WebSocket使用记录

使用视频地址 1、添加前端使用文件 2、后端配置 2.1添加依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>2.2添加websocket配置类 import org.spri…

NETSDK1141 无法解析位于 global.json 中指定的 .NET SDK 版本

1. 使用cmd命令 dotnet --info 查看自己使用的SDK版本 3.直接找到项目中的 global.json 文件&#xff0c;右键打开&#xff0c;直接修改版本为本机的SDK版本&#xff0c;就可以用了 微软文档也有详细说明: ​​​​​​NETSDK1141&#xff1a;无法解析 global.json 中指定的…

浅析CAS

CAS基本使用 以ReentrantLock为例&#xff0c;观察CAS基本使用。 class ReentrantLockExample {int a 0;// 非公平锁ReentrantLock lock new ReentrantLock(false);public void writer() {// 获取锁lock.lock();try {a;} finally {// 释放锁lock.unlock();}}public void re…

生产环境 kafka 平滑迁移之旅

文章目录 背景分析测试环境验证现实很残酷两种抉择-----leader分区切换方案选择实施步骤手工副本集增加步骤手工leader分区切换步骤 总结 背景 线上kafka集群&#xff0c;3台机器&#xff0c;3个broker&#xff1b;其中某台机器因为硬件故障&#xff0c;需要停机维修&#xff…

MSP432学习笔记12:MSP432时钟源与定时器A时钟源配置

今日深入学习一下MSP432的时钟源与配置&#xff0c; 可以结合之前的滴答计时器相关文章&#xff1a; MSP432学习笔记4&#xff1a;时钟与滴答计时器_NULL指向我的博客-CSDN博客 目录 MSP432有关时钟源系统的性能&#xff1a; 七种时钟源&#xff1a; 五种时钟&#xff1a; …

创新实践,复合机器人采摘运输教育沙盘案例研究

引言 在之前我们已经介绍了水果采摘和分拣机器人的应用场景&#xff0c;今天我们来介绍复合机器人水果采摘运输的场景。 作为最热门的技术领域&#xff0c;机器人技术正在彻底改变各行各业&#xff0c;推动全球创新。为了满足这一快速发展领域对专业技术人才日益增长的需求&a…

【Matlab】智能优化算法_广义正态分布优化算法GNDO

【Matlab】智能优化算法_广义正态分布优化算法GNDO 1.背景介绍2.数学模型2.1 局部开采2.2 全局勘探 3.文件结构4.伪代码5.参考文献 1.背景介绍 GNDO受到正态分布理论的启发。正态分布也称为高斯分布&#xff0c;是描述自然现象的一个非常重要的工具。正态分布可以定义如下。假设…