Linux学习记录——십사 进程控制(1)

news2024/10/2 22:20:39

文章目录

  • 1、进程创建
    • 1、fork函数
  • 2、进程终止
    • 1、情况分类
    • 2、如何理解进程终止
    • 3、进程终止的方式
  • 3、进程等待


1、进程创建

1、fork函数

fork函数从已存在进程中创建一个新进程,新进程为子进程,原进程为父进程。

#include <unistd.h>
pid_t fork(void);

返回值:子进程中返回0,父进程返回子进程id,出错返回-1。fork会有两个返回值,这个上一篇已经写了原因。

进程调用fork,当控制转移到内核中的fork代码后,内核做:

分配新的内存块和内核数据结构给子进程
将父进程部分数据结构内容拷贝至子进程(不会完全一样)
添加子进程到系统进程列表当中
fork返回,开始调度器调度

两个进程都没有修改数据的时候,都指向同一块物理内存空间,只有一方尝试修改数据的时候,才会再开一个空间。

写时拷贝

通常,父子代码共享,父子在不写入时,数据也是共享的,父进程按照自己的模板给子进程创建了虚拟内存,创建了页表,然后指向物理内存中同样的数据,当子进程修改数据时,系统就会拷贝一下子进程的数据,进行修改,并改变页表的映射关系,最后就指向了一个新的物理内存空间。

存在写时拷贝的意义在于,系统不允许不高效的程序出现,父进程中子进程不需要的数据子进程也不会去读取,当子进程要用到另外的空间时,写时拷贝才会出现,本质上这是一种资源筛选。

fork常规用法:

一个父进程希望复制自己,使父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数

fork调用失败的原因:

系统中有太多进程
实际用户的进程数超过了限制

2、进程终止

1、情况分类

程序正常退出,可以有结果正确或者不正确地退出。
程序崩溃,本质是进程因为某些原因,收到了操作系统给的信号,比如之前的kill -9。

对于结果是否正确,可以看程序退出码。之前我们会习惯地写,int main() return 0;如果结果正确就返回0,如果不正确,就会返回非0,这个不正确的信息会展现给用户,来判断程序的错误。

  1 mytest:mytest.c
  2     gcc -o $@ $^                                                               
  3 .PHONY:clean
  4 clean:
  5     rm -f mytest

$@表示目标文件mytest, $^表示依赖文件mytest.c

  1 #include <stdio.h>
  2 
  3 int add_to_top(int top)
  4 {
  5     int sum = 0;
  6     for(int i = 0; i < top; ++i)
  7     {
  8       sum += i;
  9     }
 10     return sum;
 11 }
 12 
 13 int main()
 14 {
 15     int result = add_to_top(100);
 16     if(result == 5050) return 0;
 17     else return 1;                                                             
 18 }

一段简短的代码。然后变成可执行程序。因为没写输出,所以我们看不到结果,但是可以通过echo $?命令来查看程序退出码

在这里插入图片描述

但是多次使用后就没有用了,因为这个命令只保留最近一次的退出码。因为上一个echo $?执行成功,所以返回0。

在这里插入图片描述

操作系统对于不同的错误都有对应的退出码,但给到用户的不能是一个数字,而是给错误信息。看一下具体的退出码。

  1 #include <stdio.h>
  2 #include <string.h>                                                                                                                                                                                                            
  3 
  4 int add_to_top(int top)
  5 {
  6     int sum = 0;
  7     for(int i = 0; i < top; i++)
  8     {
  9         sum += i;
 10     }
 11     return sum;
 12 }
 13 
 14 int main()
 15 {
 16     for(int i = 0; i <= 200; i++)
 17     {
 18         printf("%d: %s\n", i, strerror(i));
 19     }
 20    //int result = add_to_top(100);
 21    // if(result == 5050) return 0;
 22    //else return 1;
 23 }

在这里插入图片描述
总共提供了133个错误代码。

但并不是退出码和错误信息一定会对应。

2、如何理解进程终止

进程退出时,系统就需要释放对应的内核数据结构 + 代码和数据

3、进程终止的方式

除了main函数return结束,我们也可以用exit函数结束。

在这里插入图片描述

在这里插入图片描述

可以直接退出,exit里面的数字就是退出码。并且即使exit在调用的函数里面,也会直接退出,不再执行下面的代码。所以exit在代码的任何地方都可以退出进程。需要加上头文件stdlib.h。

另外一个

在这里插入图片描述

在这里插入图片描述

貌似和exit一样。但从内部来讲,exit是进行完缓冲区的数据,进行完操作后才退出,而_exit是不管不顾,直接找系统干掉这个进程,不等缓冲区刷新。库函数实现的代码里,exit是封装了_exit。

3、进程等待

子进程退出时,如果父进程不去回收,就会变成僵尸进程,会造成内存泄漏。僵尸状态的进程是无法被杀死的。

一个进程的执行是要结果的,子进程结束后用户得需要知道它的状态,代码正常跑完可以通过退出码来知道,运行异常可以通过抛出的信号来知道,所以要想知道子进程执行的结果,就要知道退出码和是否抛出异常。

所以进程等待就是通过系统调用,获取子进程退出码或者退出信号的方式,顺百年释放内存问题。

进程等待有两种方式。wait和waitpid。wait会等待所有父进程创建的子进程,如果不传参,传NULL,那么就不管结果,只回收。可以写这样一段代码来展现等待过程。

 30     pid_t id = fork();
 31     if(id == 0)
 32     {
 33         //子进程
 34         int cnt = 0;
 35         while(cnt)
 36         {
 37             printf("我是子进程, 我还活着, 我还有%dS, pid: %d, ppid%d\n", cnt--, getpid(), getppid());
 38             sleep(1);
 39         }
 40         exit(0);
 41     }
 42     sleep(10);
 43     //父进程
 44     pid_t ret_id = wait(NULL);
 45     printf("我是父进程, 我等待子进程成功, pid: %d, ppid: %d, ret_id: %d\n", getpid(), getppid(), ret_id);
 46     sleep(5);
 47     return 0; 

子进程退出时,父进程在等待,所以就可以回收子进程。

如果要获取退出结果,就要用到waitpid。

在这里插入图片描述
参数里,pid > 0,表示等待指定的进程;pid = -1,等待任一个子进程,与wait等效。如果等待成功,就会把等待的这个进程的pid返回,失败就返回-1.

第二个参数status是输出型参数,用来获取子进程的退出状态,它的退出状态就是上面3个结果,代码正常结束和抛出异常,所以这个参数就是来接收这些的。关于退出时返回的信号,可以用kill -l查看,总共64个信号,前面一半是常用的,不过没有0号信号,所以信号也是数字。乍一看status是要接收2个整数,但实际上不应以整数角度看待它,要以位图结构来看待。位图结构简而言之就是看二进制位,status是4个字节,32个比特位。
改一下之前的代码

在这里插入图片描述

上面cnt是5。exit括号里是10,但是status的结果并不是10.这时候出现数字的把它写成二进制数,左面16不看,后面16个,左面的8个是退出状态,也就是高地址的8位,这里的就是return的或者exit括号里的数字,最低的7个位如果是0,就是正常退出,然后再看退出码来判断结果是否正确。最后还剩一位是core dump标志。那我们获取这两个数。

在这里插入图片描述

这样code就是结果是否正确,signal就是正常退出。如果在子进程那里有一些异常,程序打印一次就退出了,父进程就回收它了,比如野指针等问题。把while条件改成while(1),也可以用kill -9杀死程序,父进程就回收它了。

父进程是如何获取子进程信息的?一个进程有自己的pcb,地址空间等等,进程的pcb,也就是task_struct结构体,在结构体里有两个变量,对应的就是返回值和退出信号。当进程结束时,系统会把pcb维护起来。waitpid是系统调用接口,能够访问到这个进程的pcb,然后把数据拿到,再返回给用户即可。

子进程在没有退出前,父进程会一直等待子进程死亡,这也就是一种阻塞等待。此时父进程不在运行状态,所以父进程没有运行,它在阻塞队列中待着。等到子进程结束后,pcb中某一个指向父进程的指针会去找父进程,父进程因此用阻塞状态变成运行状态,来到运行队列中,然后调用waitpid回收子进程。

用wait的时候默认是阻塞式调用,而waitpid则是在等待过程中让父进程去做其他事,保持运行状态,这是非阻塞轮询。非阻塞时调用时有三个状态,一个正常结束,一个出错,一个正在运行。如果成功,就返回子进程pid;如果第三个参数被设置,那就是非阻塞式调用,那么进程存在且正在运行,就返回0,出错返回-1.

改一下代码,变成非阻塞

在这里插入图片描述

为了让父进程做其他事情,还可以有其他办法,先放下现在的代码:

  1 #include <stdio.h>
  2 #include <string.h>
  3 #include <stdlib.h>
  4 #include <unistd.h>
  5 #include <sys/types.h>
  6 #include <sys/wait.h>

 19 int main()
 20 {
 21     /*for(int i = 0; i <= 200; i++)
 22     {
 23         printf("%d: %s\n", i, strettot(i));
 24         exit(123);
 25     }*/
 26     //int result = add_to_top(100);
 27     //if(result == 5050) return 0;
 28     //else return 1;
 29     pid_t id = fork();
 30     if(id == 0)
 31     {
 32         //子进程
 33         int cnt = 5;
 34         while(cnt)
 35         {
 36             printf("我是子进程, 我还活着, 我还有%dS, pid: %d, ppid%d\n", cnt--, getpid(), getppid());
 37             sleep(1);
 38         }
 39         if(1 == 1) exit(0);
 40         exit(10);
 41     }
 42     //父进程
 43     while(1)
 44     {
 45         int status = 0;
 46         pid_t ret_id = waitpid(id, &status, WNOHANG);
 47         if(ret_id < 0)
 48         {
 49             printf("waitpid error!\n");                                                                                                                                                                                        
 50             exit(1);
 51         }
 52         else if(ret_id == 0)
 53         {
 54             printf("子进程还没有退出,我在做做其他事情\n");
 55             sleep(1);
 56             continue;
 57         }
 58         else
 59         {
 60             printf("我是父进程, 我等待子进程成功, pid: %d, ppid: %d, ret_id: %d, child exit code: %d, child exit signal: %d\n", getpid(), getppid(), ret_id, (status>>8)&0xFF, status & 0x7F);
 61             break;
 62         }
 63 
 64     }
 65 }

人为放一些任务函数让父进程去调用, 并且写一些别的可调用的函数让父进程去做点事

   19 #define TASK_NUM 10
   20 //预设一些任务
   21 void sync_disk()
   22 {
   23     printf("这是一个刷新数据的任务\n");
   24 }
   25 
   26 void sync_log()
   27 {
   28     printf("这是一个同步日志的任务\n");
   29 }
   30 
   31 void net_send()
   32 {
   33     printf("这是一个网络发送的任务\n");
   34 }
   35 
   36 //要保存的任务相关的
   37 typedef void(*func_t)();
   38 func_t other_task[TASK_NUM] = {NULL};
   39 
   40 
   41 int LoadTask(func_t func)
   42 {
   43     int i = 0;
   44     for(; i < TASK_NUM; i++)
   45     {
   46         if(other_task[i] == NULL) break;
   47     }
   48     if(i == TASK_NUM) return -1;
   49     else other_task[i] = func;
   50     return 0;
   51 }
   52                                                                                                                                                                                                                              
   53 void InitTask()
   54 {
   55     for(int i = 0; i < TASK_NUM; i++)
   56     {
   57         other_task[i] = NULL;
   58     }
   59     LoadTask(sync_disk);
   60     LoadTask(sync_log);
   61     LoadTask(net_send);
   62 }
   63 
   64 void RunTask()
   65 {
   66     for(int i = 0; i < TASK_NUM; i++)
   67     {
W> 68         if(other_task[i] == NULL) continue;
   69         other_task[i]();
   70     }
   71 }

在//父进程之后我们这样写

   96     InitTask();
   97     //父进程
   98     while(1)
   99     {
  100         int status = 0;
  101         pid_t ret_id = waitpid(id, &status, WNOHANG);
  102         if(ret_id < 0)
  103         {
  104             printf("waitpid error!\n");
  105             exit(1);
  106         }
  107         else if(ret_id == 0)
  108         {
  109             printf("子进程还没有退出,我在做做其他事情\n");
  110             RunTask();
  111             sleep(1);
  112             continue;
  113         }
  114         else
  115         {
  116             printf("我是父进程, 我等待子进程成功, pid: %d, ppid: %d, ret_id: %d, child exit code: %d, child exit signal: %d\n", getpid(), getppid(), ret_id, (status>>8)&0xFF, status & 0x7F);
  117             break;
  118         }
  119     }

在这里插入图片描述

之前获取信号和返回码的时候,我们是自己的写的代码,也可以用给的宏来获取。

在这里插入图片描述

下一篇继续写进程相关的知识。

结束。

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

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

相关文章

论文阅读:Syntax-Aware Network for Handwritten Mathematical Expression Recognition

论文阅读&#xff1a;Syntax-Aware Network for Handwritten Mathematical Expression Recognition1 主要观点&#xff1a; 1、提出将语法信息纳入编码器-解码器网络的方法。使用一组语法规则&#xff0c;用于将每个表达式的LaTeX标记序列转换为解析树&#xff1b;用深度神经…

【vue create】一.使用vue creat搭建项目

场景&#xff1a;使用vue create脚手架快速搭建vue的项目 前提&#xff1a;需要安装node.js和cnpm以及yarn 并且cnpm需要设置为淘宝镜像&#xff0c;cnpm和yarn安装教程网上很多可以自行搜索 1.使用dos命令安装vue-cli脚手架 //这个是从镜像源下载 cnpm install -g vue/cli 查…

Google三大论文之GFS

Google三大论文之GFS Google GFS&#xff08;Google File System&#xff09; 文件系统&#xff0c;一个面向大规模数据密集型应用的、可伸缩的分布式文件系统。GFS 虽然运行在廉价的普遍硬件设备上&#xff0c;但是它依然了提供灾难冗余的能力&#xff0c;为大量客户机提供了…

接口自动化测试——多套被测环境的切换

文章目录一、意义二、实现目标三、实现方案1、使用环境管理文件2、使用不同的文件管理不同的环境&#xff08;建议使用&#xff09;3、在接口用例中指定path&#xff0c;不指定url4、环境切换a、通过环境变量进行切换b、通过命令行参数进行切换四、代码实现1、通过环境变量进行…

GPT格式的磁盘扩容

GPT格式的系统盘已经满了&#xff0c;现在需要扩充系统盘 1.怎么查看是不是GPT格式&#xff1a;fdisk -l 2.查看磁盘挂载分区情况 lsblk 2.使用parted对分区进行操作 parted /dev/sda 3.开始分区 mkpart 4.格式化sda4分区后&#xff0c;会发现分区4的文件系统已经显示为xfs…

Docker 常见操作及部署springboot、Shiro、SpringData脚手架(上)

1、查看docker 的状态 systemctl status docker 2、查看docker运行状态的详细信息 docker info 3、docker部署第一个应用 docker search nginx 拉取镜像到本地 docker pull nginx 4、查看本地的镜像信息 docker images 5、使用镜像来创建容器 docker run -d -p 1234:80 ng…

SSH配置文件解析

1.修改端口号&#xff0c;设置登录输入密码等待过期时间&#xff0c;拒绝远程登录root&#xff0c;密码为空&#xff0c;密码登录&#xff0c; [rootzzp124 ~]# vim /etc/ssh/sshd_config [rootzzp124 ~]# systemctl restart sshd.service [rootzzp124 ~]# lsof -i :2…

Superset数据探索和可视化平台入门以及案例实操

1、Superset背景 1.1、Superset概述 Apache Superset是一个现代的数据探索和可视化平台。它功能强大且十分易用&#xff0c;可对接各种数据源&#xff0c;包括很多现代的大数据分析引擎&#xff0c;拥有丰富的图表展示形式&#xff0c;并且支持自定义仪表盘。 1.2、环境说明 …

FastApi的搭建与测试

一、fastapi的安装 1-1、使用pip安装 安装fastapi的语句 pip install fastapi -i https://mirrors.aliyun.com/pypi/simple因为fastapi启动依赖于uvicorn&#xff0c;所以我们还需要安装uvicorn。 pip install uvicorn -i https://mirrors.aliyun.com/pypi/simple下面我们来…

Java学习环境一站说明(保姆级详细教学)

1.Java开发环境搭建官网下载www.oracle.com2.安装注意&#xff1a;1.选择安装位置时尽量不要安装到C盘&#xff0c;路径中不要有空格以及中文的存在2.开发人员安装的jdk中包含了jre&#xff0c;所以不需要单独安装jre3.环境变量配置打开高级系统设置2.点击环境变量3.在系统变量…

FreeSWITCH 智能呼叫流程设计

文章目录1. 智能呼叫流程2. 细节处理1. 呼叫字符串指定拨号计划2. 外呼的拨号计划3. 语音打断的支持1. 智能呼叫流程 用户与机器人对话通常都是以文本的形式进行&#xff0c;但是借助 ASR 和 TTS 技术&#xff0c;以语音电话为载体的智能呼叫系统成为可能。智能呼叫系统涉及到…

Python蓝桥杯训练:基本数据结构 [二叉树] 上

Python蓝桥杯训练&#xff1a;基本数据结构 [二叉树] 上 文章目录Python蓝桥杯训练&#xff1a;基本数据结构 [二叉树] 上一、前言二、有关二叉树理论基础1、二叉树的基本定义2、二叉树的常见类型3、二叉树的遍历方式三、有关二叉树的层序遍历的题目1、[二叉树的层序遍历](http…

网络原理之传输层协议,TCP中的主要核心机制(重点)

目录 一. 传输层中的端口号 二. UDP协议 三. TCP协议 四. TCP中的核心机制 1. 确认应答 2. 超时重传 3. 连接管理 建立连接(三次握手) 断开连接(四次挥手) 4. 滑动窗口 考虑丢包情况1&#xff1a;ack丢了 考虑丢包情况2&#xff1a;数据丢了 5. 流量控制 6. 拥塞…

学这些语言工作更吃香,Python虽然再次摘得桂冠,但在就业需求中位居第一的还得是它!

2022 IEEE 编程语言榜单发布&#xff01; IEEE Spectrum 2022 编程语言排名前十的分别是&#xff1a;Python&#xff0c;C&#xff0c;C&#xff0c;C#&#xff0c;Java&#xff0c;SQL&#xff0c;JavaScript&#xff0c;R&#xff0c;HTML&#xff0c;TypeScript。 一. Pyth…

嵌入式学习笔记——寄存器实现控制LED小灯

文章目录前言GPIO通用输出模式初始化LED小灯的GPIO原理图初始化代码初始化的效果功能函数封装直接分开宏定义两个使用条件运算符封装函数实现简单的功能前言 上一篇中&#xff0c;介绍了GPIO相关的所有寄存器&#xff0c;并在最后简单实现了一个LED灯的控制&#xff0c;由于那…

以获取笔记本电池信息为例介绍WMI的使用

注&#xff1a;本人也还没有完全弄懂WMI的原理&#xff0c;以下内容仅供参考。。。 简单来说&#xff0c;比起Win32提供的接口&#xff0c;WMI可以提供更多的系统信息&#xff0c;它本身是一个数据库架构&#xff0c;通过它可以访问、配置、管理和监视几乎所有的Windows资源&…

AM5728(AM5708)开发实战之移植OpenCV-3.4.11

一 概述 OpenCV是一个开源的跨平台计算机视觉库&#xff0c;可以运行在Linux、Windows、Mac OS等操作系统上&#xff0c;它为图像处理、模式识别、三维重建、物体跟踪、机器学习提供了丰富的算法。 由于OpenCV依赖包特别多&#xff0c;尽量不要使用交叉编译&#xff0c;即在什…

VMware虚拟机搭建环境通用方法

目录一、前期准备1.下载并安装一个虚拟机软件二、开始创建虚拟机1.配置虚拟机硬件相关操作2.虚拟机网络相关操作三、开机配置相关内容0.开机遇到报错处理&#xff08;选看--开机没有报错请忽略&#xff09;1.开始配置2.开机之后配置3.使用xshell远程登录4.使用xshell配置虚拟机…

下一个7年,保持期待、持续思考,酷雷曼继续向前!

过去7年&#xff0c;我们一直在思考&#xff0c; VR技术究竟能为我们的生活带来什么&#xff1f; 是足不出户就能云游千里的秀美风光&#xff1f; 是在家就能沉浸式体验线上消费的便利&#xff1f; 还是为商企和用户搭建更快速的沟通桥梁&#xff1f; NO.1、技术变革 在信…

磁盘阵列Raid探讨

最近公司买服务器&#xff0c;顺便了解一下服务器配置方面的问题 以下讨论的都是入门级服务器配置&#xff0c;全部是主观意见&#xff0c;没有任何科学依据&#xff0c;欢迎大家讨论 Raid0&#xff0c;Raid1&#xff0c;Raid10&#xff0c;Raid5&#xff0c;Raid6(Raid5热备)…