【Linux】进程控制

目录

  • 一、进程创建
    • 初识fork函数
    • fork函数返回值
    • 写时拷贝
    • fork常规用法
    • fork调用失败的原因
  • 二、进程终止
    • 进程退出场景
    • 进程常见退出方法
      • _exit函数与exit函数
  • 三、进程等待
    • 进程等待必要性
    • 进程等待的方法
      • wait
      • waitpid
    • 获取子进程status
    • 非阻塞等待测试
  • 四、进程程序替换
    • 替换原理
    • 替换函数
    • 函数解释
    • 命名理解
  • 五、简易的shell

一、进程创建

初识fork函数

在Linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
在这里插入图片描述
返回值:在子进程中返回0,父进程返回子进程的PID,子进程创建失败返回-1。

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

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

例子:
在这里插入图片描述
运行结果:
在这里插入图片描述
这里可以看到三行输出,一行Before,两行After。进程30363先打印Before消息,然后它再打印After。另一个After消息由进程30364打印的。注意到进程30364没有打印Before,为什么呢?
因为Before是由父进程打印的,而调用fork函数之后,则是由父进程和子进程两个进程分别打印After。也就是说,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。
注意: fork之后,父进程和子进程谁先执行完全由调度器决定。

fork函数返回值

子进程返回0,
父进程返回的是子进程的pid。

那么为什么fork有两个返回值?
因为在函数内部准备执行return的时候,我们的主题功能就已经完成了,也就是子进程就已经创建完毕了,那么之后的父进程和子进程都执行了return,所以就返回了两个值。

写时拷贝

在子进程刚刚创建的时候,父子进程的代码是共享的,父子在不写入时,数据也是共享的,只有当任意一方准备写入时,便各自拷贝一份副本,如下图所示:
在这里插入图片描述
而这种按需申请资源的策略就是写时拷贝

为什么数据要写时拷贝?
因为进程具有独立性。进程的之间的运行是互不影响的,数据和代码是分开的,代码是共用的,而数据是各自用各自的,不能让一个进程的修改影响到另一个进程,所以就有了写时拷贝,在需要修改数据的时候再分配,这样便可以高效的使用内存空间。

在这里插入图片描述
运行结果:
在这里插入图片描述
可以看到子进程对全局数据进行修改,由于进程具有独立性,独立性体现在数据层面,在子进程对数据进行修改时,进行了写时拷贝,所以并不影响父进程。

fork常规用法

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

fork调用失败的原因

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

二、进程终止

进程退出场景

进程退出只有三种情况:

  • 代码运行完毕,结果正确
  • 代码运行完毕,结果不正确
  • 代码异常终止(进程崩溃)

进程常见退出方法

进程退出都会有一个进程退出码,我们一般以0表示代码正常执行完毕,以非0表示代码执行过程中出现错误,我们可以使用echo $?命令查看最近一次进程退出的退出码信息。
我们看看下面这个代码:
在这里插入图片描述
在这里插入图片描述
我们可以看到main函数是正常执行完了。

我们也可以通过C语言中的strerror函数打印该错误码在C语言中所对应的错误信息,如下:
在这里插入图片描述
在这里插入图片描述

_exit函数与exit函数

使用exit函数退出进程也是我们常用的方法,exit函数可以在代码的任意地方调用该函数都表示进程退出,但在调用exit之前,还做了其他工作:

  1. 执行用户通过 atexit 或 on_exit 定义的清理函数。
  2. 关闭所有打开的流,所有的缓存数据均被写入
  3. 调用_exit

执行exit(n)等同于执行return n, 因为调用main的运行时函数会将main的返回值当做 exit 的参数。
例如,如下代码中,exit函数终止进程前会将缓冲区当中的数据输出:
在这里插入图片描述
在这里插入图片描述
但是,_exit函数是直接干掉进程,不会对缓冲区数据进行刷新。
如下:
在这里插入图片描述
在这里插入图片描述

三、进程等待

进程等待必要性

  1. 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  2. 另外,进程一旦变成僵尸状态,那就刀枪不入,kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  3. 最后,父进程派给子进程的任务完成的如何,我们需要知道,子进程运行完成,结果对还是不对,或者是否正常退出。
  4. 父进程通过进程等待的方式,回收子进程资源,避免内存泄漏,获取子进程退出信息

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

进程等待的方法

wait

pid_t wait(int* status);

返回值:成功则返回被等待进程pid,失败则返回-1。
参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL。

如下,父进程会等带子进程执行完毕:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if (id == 0){// childint count = 3;while (count--){printf("I am child,PID:%d, PPID:%d\n", getpid(), getppid());sleep(1);}exit(0);}// fatherpid_t ret = wait(NULL);if (ret > 0){// wait successprintf("wait child success...\n");}sleep(3);return 0;
}

在这里插入图片描述
我们先用监控脚本对进程进行实时监控:
在这里插入图片描述
我们可以看到子进程退出后,父进程回收了子进程的退出信息,回收了内存空间,子进程也就不会变成僵尸进程了。

waitpid

pid_ t waitpid(pid_t pid, int * status, int options);

返回值:

  • 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

  • pid:
    Pid=-1,等待任一个子进程。与wait等效。
    Pid>0.等待其进程ID与pid相等的子进程。
  • status:
    WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options: 当设置为WNOHANG时,若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

例如:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if (id == 0){int cnt = 5;while (cnt--){printf("我是子进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}exit(0);}int status = 0;pid_t ret_id = waitpid(id, &status, 0);printf("我是父进程,等待子进程成功,pid: %d, ppid: %d\n", getpid(), getppid());return 0;
}

在这里插入图片描述

注意:

  • 如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。
  • 如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
  • 如果不存在该子进程,则立即出错返回。

获取子进程status

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待,不同比特位所代表的信息不同。

在这里插入图片描述

我们可以通过为操作,查看根据status得到的进程的退出码和退出信号。

(status >> 8) & 0xFF;//退出码
status & 0x7F;//退出信号

如下:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{pid_t id = fork();if (id == 0){int cnt = 5;while (cnt--){printf("我是子进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}exit(111);}int status = 0;pid_t ret_id = waitpid(id, &status, 0);printf("我是父进程,等待子进程成功,pid: %d, ppid: %d, ret_id: %d, status: %d, child exit code: %d, child exit siginal: %d\n",getpid(), getppid(), ret_id, status, (status >> 8) & 0xFF, status & 0x7F);return 0;
}

在这里插入图片描述
注意:退出信号为0,则表示代码正常,非0,则表示代码异常。

非阻塞等待测试

父进程一直调用wait/waitpid进行等待,这是阻塞等待。
而可以让父进程不用一直等待子进程退出,而是当子进程未退出时父进程不占用资源,做自己的事情,当子进程退出时再读取子进程的退出信息,即非阻塞等待,那么如何做到呢?
把waitpid 的第三个参数写成 WNOHANG 即可。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>#define TASK_NUM 10// 预设一批任务
void sync_disk()
{printf("这是一个刷新数据的任务\n");
}void sync_log()
{printf("这是一个同步日志的任务\n");
}void sync_send()
{printf("这是一个进行网络发送的任务\n");
}typedef void (*func_t)();
func_t other_task[TASK_NUM] = {NULL};int LoadTask(func_t func)
{int i = 0;for (; i < TASK_NUM; i++){if (other_task[i] == NULL)break;}if (i == TASK_NUM)return -1;elseother_task[i] = func;return 0;
}void InitTask()
{int i = 0;for (i = 0; i < TASK_NUM; i++){other_task[i] = NULL;}LoadTask(sync_disk);LoadTask(sync_log);LoadTask(sync_send);
}void RunTask()
{int i = 0;for (i = 0; i < TASK_NUM; i++){if (other_task[i] == NULL)continue;other_task[i]();}
}
int main()
{pid_t id = fork();if (id == 0){int cnt = 5;while (cnt--){printf("我是子进程, pid: %d, ppid: %d\n", getpid(), getppid());sleep(1);}exit(111);}InitTask();while (1){int status = 0;pid_t ret_id = waitpid(id, &status, WNOHANG);if (ret_id < 0){printf("error\n");exit(1);}else if (ret_id == 0){RunTask();sleep(1);continue;}else{if (WIFEXITED(status)){printf("wait success child exit code: %d\n", WEXITSTATUS(status));}else{printf("wait success child exit siginal: %d\n", status & 0x7F);}}}return 0;
}

在这里插入图片描述

四、进程程序替换

创建子进程的目的是什么?
1、让子进程执行父进程的一部分代码
2、如果子进程想指向一个全新的程序代码,便有了进程程序替换

替换原理

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
在这里插入图片描述
创建进程的时候,OS 先把对应的数据结构内核的PCD空间先创建出来,然后在需要的时候,再通过 execl 把外部的代码录制到内存里、

替换函数

其实有六种以exec开头的函数,统称exec函数:

  • int execl(const char *path, const char *arg, …);
  • int execlp(const char *file, const char *arg, …);
  • int execle(const char *path, const char *arg, …,char *const envp[]);
  • int execv(const char *path, char *const argv[]);
  • int execvp(const char *file, char *const argv[]);

代码演示:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
int main()
{pid_t id = fork();if (id == 0){// childprintf("我是子进程:%d\n", getpid());// execl("/bin/ls", "ls", "-a", "-l", NULL);char *const myargv[] = {"ls","-a","-l","-n",NULL};execv("/bin/ls", myargv);// execlp("ls","ls", "-a", "-l", NULL);// char *const myargv[] = {//     "ls",//     "-a",//     "-l",//     "-n",//     NULL};// execvp("ls",myargv);exit(1);}sleep(5);// faterint status = 0;printf("我是父进程\n");waitpid(id, &status, 0);printf("child exit code: %d\n", WEXITSTATUS(status));return 0;
}

在这里插入图片描述

函数解释

  • 这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回。
  • 如果调用出错则返回-1
  • 所以exec函数只有出错的返回值而没有成功的返回值。

命名理解

  • l(list) : 表示参数采用列表
  • v(vector) : 参数用数组
  • p(path) : 有p自动搜索环境变量PATH
  • e(env) : 表示自己维护环境变量
    在这里插入图片描述下图是exec函数族一个完整的例子:
    在这里插入图片描述

五、简易的shell

用下图的时间轴来表示事件的发生次序。其中时间从左向右。shell由标识为sh的方块代表,它随着时间的流逝从左向右移动。shell从用户读入字符串"ls"。shell建立一个新的进程,然后在那个进程中运行ls程序并等待那个进程结束。
在这里插入图片描述
然后shell读取新的一行输入,建立一个新的进程,在这个进程中运行程序 并等待这个进程结束。
要写一个shell,需要循环以下过程:

  1. 获取命令行
  2. 解析命令行
  3. 建立一个子进程(fork)
  4. 替换子进程(execvp)
  5. 父进程等待子进程退出(wait)

代码实现:

#include <stdio.h>
#include <assert.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <stdlib.h>#define MAX 1024
#define ARGC 64
#define SEP " "int split(char *commandstr, char *argv[])
{assert(commandstr);assert(argv);argv[0] = strtok(commandstr, SEP);if (argv[0] == NULL)return -1;int i = 1;while (argv[i++] = strtok(NULL, SEP));// while (1)// {//     argv[i] = strtok(NULL, SEP);//     if (argv[i] == NULL)//         break;//     i++;// }return 0;
}void debugPrint(char *argv[])
{int i = 0;for (i = 0; argv[i]; i++){printf("%d : %s\n", i, argv[i]);}
}int main()
{char commandstr[MAX] = {0};char *argv[ARGC] = {NULL};while (1){printf("[zhangsan@mymachine currpath]#");fflush(stdout);char *s = fgets(commandstr, sizeof(commandstr), stdin);assert(s);(void)s;commandstr[strlen(commandstr) - 1] = '\0';int n = split(commandstr, argv);assert(n == 0);if (n != 0)continue;debugPrint(argv);pid_t id = fork();assert(id >= 0);(void)id;if (id == 0){// childexecvp(argv[0],argv);exit(1);}int status = 0;waitpid(id, &status, 0);// printf("%s\n",commandstr);}return 0;
}

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

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

相关文章

Python WEB框架FastAPI (二)

Python WEB框架FastAPI &#xff08;二&#xff09; 最近一直在使用fastapi&#xff0c;随着使用的深入发现我对于它的了解还是太少了&#xff0c;以至于踩了一些坑。所以在这里记录一下&#xff0c;愿看到的小伙伴不迷路。 路径传参并发问题 一、路径传参 这是对上一个传参…

数字化管理新革命,AI数字人CEO登场引领变革!

王一博老板乐华娱乐CEO杜华推出了她的双生数字人华华子&#xff0c;专门替自己直播卖货。在没有任何宣传的情况下&#xff0c;仅仅在短短的10分钟直播时间内&#xff0c;观众人数就飙升至30万人&#xff01;同时&#xff0c;“杜华AI华华子直播”更是迅速登上了微博热搜榜。这一…

【Mysql】数据库第四讲(表的增删改查操作 超全面 附实操案例)

表的查询 1.Create 表的创建1.1单行插入多行插入1.2替换 2.Retrieve 读取2.1全列查询2.2指定列查询2.3查询字段为表达式2.4为查询结果指定别名2.5结果去重2.6WHERE条件2.7结果排序2.8筛选分页结果 3.Update更新案例 4.Delete删除案例截断表插入查询结果 5.聚合函数 1.Create 表…

Intellij idea 2023 年下载、安装教程、亲测可用

文章目录 1 下载与安装IDEA2 常用设置设置 Java JDK 版本自动导入包、移除包IDEA 自动生成 author 注释签名java.io.File 类无法自动提示导入&#xff1f;高亮显示与选中字符串相同的内容IDEA 配置 MavenIDEA 连接 Mysql 数据库 3 参考文章 1 下载与安装IDEA 首先先到官网下载…

centos设置固定ip

ip addr查看是哪张网卡我这里是 编辑 设置

【flutter】架构之商城main入口

架构之商城main入口 前言一、项目模块的划分二、入口main的配置三、配置文件怎么做总结 前言 本栏目我们将完成一个商城项目的架构搭建&#xff0c;并完善中间的所有功能&#xff0c;总页面大概200个&#xff0c;如果你能看完整个栏目&#xff0c;你肯定能独立完成flutter 项目…

基于自编译的onlyoffice镜像,关于修改字体的问题

基于自编译的onlyoffice镜像&#xff0c;关于修改字体的问题 自编译onlyoffice镜像来自于 https://blog.csdn.net/Gemini1995/article/details/132427908 该镜像里面没有documentserver-generate-allfonts.sh文件&#xff0c;所以需要自己创建一个&#xff08;建议放在/usr/b…

接入网络技术

接入网络&#xff1a;是实现网络边缘的端系统与网络核心连接与接入的网络。 常见有以下几类&#xff1a; 1、电话拨号接入&#xff1a;这类接入方式在早期接入网络中主要用于家庭接入&#xff0c;利用了电话网络覆盖广泛的优点&#xff0c;能够方便地实现分散的家庭用户接入网…

八、实时时钟

八、实时时钟 简介时钟芯片模块代码可调时钟 简介 引脚定义和应用电路 我们的开发板没有备用电池 寄存器定义 时序定义 在时钟的上升沿&#xff0c;IO口的数据被写入到芯片中&#xff0c;在下降沿&#xff0c;芯片就会将数据输出。如果是写入&#xff0c;那么在整个过程中&…

学习笔记|外部中断|INT0|中断列表|STC32G单片机视频开发教程(冲哥)|第十五集:中断系统和外部中断

文章目录 1.中断和中断系统1.1什么是中断?1.2什么是中断系统1.3中断系统的优点1.4 中断系统包含哪些中断源1.5.中断次序 2.什么是外部中断3.外部中断的用法4.外部中断的用法新的测试场景完整代码 总结课后练习: 上节课我们学完了GPIO的矩阵按键&#xff0c;已经把这个GPIO的一…

视频讲解|1033含sop的配电网重构(含风光可多时段拓展)

目录 1 主要内容 程序特点 讲解重点 2 视频链接 1 主要内容 该视频为含sop的配电网重构matlab代码讲解&#xff0c;对应资源下载链接为含sop的配电网重构&#xff08;含风光|可多时段拓展&#xff09;&#xff0c;程序主要内容是&#xff1a;针对含sop的配电网重构模型&…

基于Java的公务员考试资料共享平台的设计与实现

前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f447;&#x1f3fb;…

【Linux基础】第28讲 Linux Vi编辑器

在Linux下一班使用Vi编辑器来编辑文件vi既可以查看文件也可以编辑文件而vim是vi的升级版本&#xff0c;具备更多的功能。vi如果目标文件不存在&#xff0c;会创建新的文件。但如果新文件没做编辑&#xff0c;退出后还会消失。 VI的三种模式介绍 三种模式&#xff08;状态&…

UML基础与应用之面向对象

UML&#xff08;Unified Modeling Language&#xff09;是一种用于软件系统建模的标准化语言&#xff0c;它使用图形符号和文本来描述软件系统的结构、行为和交互。在面向对象编程中&#xff0c;UML被广泛应用于软件系统的设计和分析阶段。本文将总结UML基础与应用之面向对象的…

网络爬虫-----爬虫的分类及原理

目录 爬虫的分类 1.通用网络爬虫&#xff1a;搜索引擎的爬虫 2.聚焦网络爬虫&#xff1a;针对特定网页的爬虫 3.增量式网络爬虫 4.深层网络爬虫 通用爬虫与聚焦爬虫的原理 通用爬虫&#xff1a; 聚焦爬虫&#xff1a; 爬虫的分类 网络爬虫按照系统结构和实现技术&#…

Linux——IO

✅<1>主页&#xff1a;&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;Linux——文件系统 ☂️<3>开发环境&#xff1a;Centos7 &#x1f4ac;<4>前言&#xff1a;是不是只有C/C有文件操作呢&#xff1f;python&#xff0c;java&…

长尾关键词挖掘软件-免费的百度搜索关键词挖掘

嗨&#xff0c;大家好&#xff01;今天&#xff0c;我想和大家聊一聊长尾关键词挖掘工具。作为一个在网络世界里摸爬滚打多年的人&#xff0c;我对这个话题有着一些个人的感悟和见解&#xff0c;希望能与大家分享。 首先&#xff0c;让我坦白一点&#xff0c;长尾关键词挖掘工具…

《计算机视觉中的多视图几何》笔记(3)

3 Projective Geometry and Transformations of 3D 这章主要讲的是3D的射影几何&#xff0c;与2D的射影几何差不多。主要区别是&#xff1a; 3D射影几何对偶的是点和平面&#xff0c;直线是自对偶的。3D空间中直线有4个自由度&#xff0c;这一现象并不是那么容易直接得出。一…

2023最新安装微信小程序开发软件安装教程

一&#xff0c;安装开发者工具 我们在开发小程序之前&#xff0c;首先需要安装小程序开发者工具&#xff0c;今天就来教大家安装小程序开发者工具。 微信开放文档 (qq.com)https://developers.weixin.qq.com/miniprogram/dev/framework/ 官网工具下载地址&#xff1a; 微信…

在windows下持续ping ip,将返回结果及时间记录到文件中

在纯英文路径下创建文件ping.txt 在txt中写入 Dim args, flag, unsuccOut args"" otherout"" flag0If WScript.Arguments.count 0 Then WScript.Echo "Usage: cscript tping.vbs [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]" WScr…