Linux系统编程:进程控制

1.进程创建

1.1 fork函数

        fork()通过复制调用进程来创建一个新进程。新进程称为子进程,是调用进程的精确副本
进程,但以下几点除外:

  • 子进程有自己的PID,此PID与任何现有进程组的ID不匹配
  • 子进程的父进程ID与父进程的进程ID相同。
  • 子进程没有继承父进程的内存锁
  • 进程资源利用率(getrusage(2))和CPU时间计数器(times(2))在子进程中重置为零
  • 子进程的挂起信号集最初为空
  • 子进程不能继承父进程的信号调整
  • 子进程不从父进程继承记录锁
  • 子进程不从父进程继承计时器
  • 子进程不继承父进程未完成的异步I/O操作,也不继承任何异步操作从它的父进程中获取同步I/O上下文

#include <unistd.h>
pid_t fork();
返回值:fork成功则子进程PID被返回给父进程,0被返回给子进程。失败,-1被返回给父进程,没有子进程创建。-- 给父进程返回子进程的pid,子进程返回0,是因为一个父进程可以有多个子进程,儿子进程只能有一个父进程。

 当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可执行自己的代码,看如下程序:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>int g_val = 0;
int main()
{pid_t id = fork();if(id < 0){perror("fork");return 0;}else if(id == 0){ //childg_val = 100;printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);}else{ //parentprintf("parent[%d]: %d : %p\n", getpid(), g_val, &g_val);}sleep(1);return 0;
}

执行结果如下: 

 

由结果可以得出,fork之后,父子进程各自执行自己的代码块

fork调用失败的原因: 系统中有太多的进程 实际用户的进程数超过了限制

1.2 写时拷贝

        通常,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副 本。具体见下图:

2.进程终止

进程退出场景:

  • 代码运行结束,结果正确
  • 代码运行结束,结果错误
  • 代码异常终止

 进程退出方式:

  • 从main返回 return n; 
    执行 return n 等同于执行 exit(n), 因为调用 main 的运行时函数会将 main 的返
    回值当做 exit 的参数。
  • 在任意地方调用exit(errno) -- 库函数,终止进程,主动刷新缓冲区
  • _exit() -- 系统调用,终止进程不会刷新缓冲区
  • ctrl + c -- 信号终止

 echo $?  :看记录最后一个进程在命令行执行完毕时对应的退出码

#include <unistd.h>
void _exit(int status);

参数:status定义了进程的终止状态,父进程通过wait来获取该值 虽然statusint,但是仅有低8位可以被父进程所用

#include <unistd.h>
void exit(int status);

exit最后也会调用exit, 但在调用exit之前,还做了其他工作:

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

3.进程等待 

        检测子进程推出信息,将子进程的退出信息通过status拿回来。

3.1 进程等待必要性

  • 之前讲过,子进程退出,父进程如果不管不顾,就可能造成 僵尸进程 ,进而造成内存泄漏。
  • 另外,进程一旦变成僵尸状态,那就刀枪不入, kill -9 也无能为力
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

3.2 进程等待的方法

wait方法

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int* status);
参数:获取子进程的退出状态

返回值:成功,返回终止子进程的pid,失败返回-1

 waitpid方法

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(pid_t pid, int *status, int options);
参数:pid:pid=-1,等待任意子进程,与wait等效;pid>0,等待其进程id与pid相等的子进程

           status:输出型参数,拿到子进程的退出结果。-- 有自己的为图结构,只关心低16
                        个比特位()0-15,
                        次低八位(8-15):进程退出状态(结果是否正确) -- 设为:(status>>8) &0XFF
                        低七位(0-7):进程终止信号(是否正常结束) -- 设为:status&0X7F
           options:先设为0
返回值:成功,返回收集到的子进程的pid

              若无可以收集的一推出的子进程,return 0;

               若调用出错,返回-1,这时,errno会被设置为相应的值,以指示错误所在。

 WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

WEXITSTATUS(status): WIFEXITED非零,提取子进程退出码。(查看进程的退出码)        

 下面看一段示例代码:

#include<stdlib.h>
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>int main()
{pid_t id = fork();if(id == 0){int cnt = 5;while(cnt){printf("我是子进程pid:%d, 父进程:%d, cnt = %d\n", getpid(), getppid(), cnt--);sleep(1);}// int d = 10/0;exit(10);}sleep(7);int status = 0;pid_t ret = waitpid(id, &status, 0);if(id > 0){printf("wait success: %d, sig_number: %d, child_exit_code: %d\n", ret, (status & 0x7F), (status>>8)&0xFF);}sleep(5);return 0;
}

结果下图所示:

第一个图为我执行kill -9 22287得到的结果,进程收到9号信号,进程终止

第二个图为程序正常运行结束的结果。

阻塞等待和非阻塞等待:

        阻塞等待:子进程未推出时,父进程不调用一直等待直到子进程结束。

        非阻塞等待:在与逆行期间一直询问子进程是否结束 waitpid()函数第三个参数设置为WNOHANG。
非阻塞等待的好处:

  • 不会占用父进程的所有精力,可以在轮询期间做别的事情

下面为非阻塞式等待的代码:

#include <stdio.h> 
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t pid;pid = fork();if(pid < 0){printf("%s fork error\n",__FUNCTION__);return 1;}else if( pid == 0 ){ //childprintf("child is run, pid is : %d\n",getpid());sleep(5);exit(1);} else{int status = 0;pid_t ret = 0;do{ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待if( ret == 0 ){printf("child is running\n");}sleep(1);}while(ret == 0);if( WIFEXITED(status) && ret == pid ){printf("wait child 5s success, child return code is :%d.\n",WEXITSTATUS(status));}else{printf("wait child failed, return.\n");return 1;}}return 0;
}

可以看到,父进程在等待子进程的同时还会执行打印语句。若为非阻塞等待,会一直卡在等待的环节。 

总结:

进程等待:

  • 是什么? -- 通过系统调用,让父进程等待子进程的方式
  • 为什么? -- 释放子进程的僵尸状态,获取子进程状态
  • 怎么等? -- wait/waitpid  阻塞等待/非阻塞等待

4.进程程序替换

        相当于用自己的程序把别人的程序跑起来,支持不同语言(任何后端语言)的替换

4.1 创建子进程的目的

  • 想让子进程执行父进程代码的一部分 -- 执行父进程对应的磁盘代码中的一部分
  • 想让子进程执行一个全新的程序 -- 让紫禁城想办法,家在磁盘上指定的程序,执行新程序的代码和数据

4.2 替换原理

        就是将指定程序的代码和数据加载带指定的位置,进程替换时并没有创建新的进程。在子进程中调用execl函数并不会影响父进程的执行(进程具有独立性)。OS感觉到替换后,则进行写时拷贝,重新分配内存--子进程通过页表重新映射到新的内存。

4.3 替换函数

#include <unistd.h>`
int execl(const char *path, const char *arg, ...);
参数:path:路径
            arg:在命令行怎么执行就怎么传参
...:可变参数列表
返回值:执行失败返回-1,并继续执行源代码,成功不会返回值。
int execlp(const char *file, const char *arg, ...);
p:代表的就是如何找程序的功能,带p字符的函数,不用告诉我替换程序的路径,只需要知道时谁,会自动在环境变量PATH中进行可执行程序的查找。
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
v
:vector,可以将所有的可执行参数,放入到数组(必须以空作为结束)中,不用可变参数传参
int execvp(const char *file, char *const argv[]);
 
int execve(const char *path, char *const argv[], char *const envp[]); -- 允许自定义环境变量

所有的execl* 系列的接口都必须以NULL结尾 

下面为各个接口的演示:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/wait.h>
#include<sys/types.h>int main()
{// 用我们的程序将别人的程序执行起来// .c -> exe -> load -> process -> 运行 -> 执行我们现在所写的代码printf("process is running...\n");// 只要是个函数,掉用就有可能失败 就是没有替换成功 继续执行下面的原代码// 只有错误时会返回,返回值为-1execl("/usr/bin/ls"/*要执行哪个程序*/, "ls","-a", "-l", "--color=auto", NULL/*你想怎么执行*/);  // 所有的execl* 系列的接口都必须以NULL结尾printf("execl\n");// 为何下面的printf没有执行呢? printf是在execl之后的,execl执行完毕后,代码已经完全被替换 开始新的程序的代码了printf("process running done...\n");return 0;
}

 这个代码使用execl接口将我们的程序替换为了命令行命令,ls -a -l,结果如下:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/wait.h>
#include<sys/types.h>int main(int argc, char *argv[])
{printf("process is running\n!");pid_t id = fork();assert(id!=-1);if(id == 0){sleep(1);execlp("ls", "ls","-a", "-l", NULL);printf("原子进程!\n");exit(10);}// 下面为父进程的代码,子进程的替换不会影响父进程的代码int status = 0;int ret = waitpid(id, &status, 0);if(ret > 0) printf("wait success: exit code:%d, sig:%d\n", (status>>8) & 0xFF, status & 0x7F);return 0;
}

 有这段代码也可以看出,子进程在进行程序替换时,是不会影响父进程的

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/wait.h>
#include<sys/types.h>int main(int argc, char *argv[])
{printf("process is running\n!");pid_t id = fork();assert(id!=-1);if(id == 0){sleep(1);char *arv_[] = { "ls","-a", "-l", NULL };execv("/usr/bin/ls", arv_);exit(10);}// 下面为父进程的代码,子进程的替换不会影响父进程的代码int status = 0;int ret = waitpid(id, &status, 0);if(ret > 0) printf("wait success: exit code:%d, sig:%d\n", (status>>8) & 0xFF, status & 0x7F);return 0;
}

其他几个与上面的类似,这里就不一一列举了。除了可以替换系统命令,还可以将程序替换为自己的程序,下面用一个接口execvp来演示。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<assert.h>
#include<sys/wait.h>
#include<sys/types.h>int main(int argc, char *argv[])
{printf("process is running\n!");pid_t id = fork();assert(id!=-1);if(id == 0){sleep(1);char *arv_[] = { "mycpp", NULL };execvp("./mybin", arv_);exit(10);}// 下面为父进程的代码,子进程的替换不会影响父进程的代码int status = 0;int ret = waitpid(id, &status, 0);if(ret > 0) printf("wait success: exit code:%d, sig:%d\n", (status>>8) & 0xFF, status & 0x7F);return 0;
}

 

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

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

相关文章

Uncaught InternalError: too much recursion

今天在敲代码的时候偶然间发现项目因为一次操作导致浏览器变得非常卡&#xff0c;而且控制台还报错了 Uncaught InternalError: too much recursior 页面截图如下 &#xff1a; 突如起来的报错和页面异常卡顿给我整不会了ovo&#xff0c;点开报错的地方&#xff0c;直接跳转到对…

HTML满屏漂浮爱心

目录 写在前面 满屏爱心 代码分析 系列推荐 写在最后 写在前面 小编给大家准备了满屏漂浮爱心代码&#xff0c;一起来看看吧~ 满屏爱心 文件heart.svg <svg xmlns"http://www.w3.org/2000/svg" width"473.8px" height"408.6px" view…

Ansible的安装与配置

Ansible的安装与配置 1. 安装ansible # ansible是epel源提供的&#xff0c;所以必须安装epel&#xff1a; [rootRocky9 ~]# yum -y install epel-release Last metadata expiration check: 0:01:53 ago on Tue 26 Dec 2023 10:05:34 PM CST. Dependencies resolved. Package …

精品录播|电磁场数值仿真技术及天线设计与应用

电磁场数值仿真技术及天线设计与应用

直播预约丨《袋鼠云大数据实操指南》No.2:实时开发,如何成为数据智能化的有效驱动力

近年来&#xff0c;新质生产力、数据要素及数据资产入表等新兴概念犹如一股强劲的浪潮&#xff0c;持续冲击并革新着企业数字化转型的观念视野&#xff0c;昭示着一个以数据为核心驱动力的新时代正稳步启幕。 面对这些引领经济转型的新兴概念&#xff0c;为了更好地服务于客户…

银河麒麟V10桌面版分区分析

前言&#xff1a;本文只讨论gpt分区uefi引导形式 &#xff0c;了解分区方案的目的是方便恢复&#xff0c;还原&#xff0c;扩容等&#xff0c;普通用户使用无需了解这些细节。 先回顾分析windows和ubuntu默认分区用做对比 1、windows11默认分区 win11分区&#xff0c;如上图&am…

【Linux线程(二)】线程互斥和同步

前言&#xff1a; 在上一篇博客中&#xff0c;我们讲解了什么是线程以及如何对线程进行控制&#xff0c;那么了解了这些&#xff0c;我们就可以在程序中创建多线程了&#xff0c;可是多线程往往会带有许多问题&#xff0c;比如竞态条件、死锁、数据竞争、内存泄漏等问题&#…

高扩展性便携式1553B总线测试仪,支持麒麟操作系统

手提式便携1553B总线测试仪&#xff0c;利用订制平台的PXIe扩展槽嵌入石竹科技自主研发的高性能T系列专用1553B测试板卡和高级协议分析和测试软件FP-1553B Pro、FP-5186集成的一款模块化、功能可订制的测试仪器。 基本配置可对1553B信号进行波形采集&#xff08;提供软件示波器…

Charles抓包工具使用指南:成为网络数据侦察高手

在日常的开发和测试工作中&#xff0c;能够洞察网络请求和响应的细节对于诊断问题、优化性能及保障数据安全至关重要。Charles作为一款流行的网络抓包和调试工具&#xff0c;凭借其强大的功能和直观的界面成为许多开发人员和测试工程师的必备神器。那么&#xff0c;如何高效地使…

Android RecyclerView注册每项的单击和长按事件监听

1.在Adapter中定义监听接口 public class DeviceListAdapter extends RecyclerView.Adapter<DeviceViewHolder> {public interface OnItemClickListener {public void onItemClick(View view, int position);public void onItemLongClick(View view, int position);}pri…

【批量处理文档】CleverPDF,WOED,EXCEL,PPT,PDF批量转换相关的工具网站NO.97

本文一共:429 个字,需要阅读:2 分钟,更新时间:2024年5 月14日,部分内容具有时效性,如有失效请留言,阅读量:1 CleverPDF&#xff0c;具有44个功能强大的免费在线PDF转换器和工具。 资源来源于网络&#xff0c;免费分享仅供学习和测试使用&#xff0c;请勿用于商业用途&#xff…

超级漂亮的 Ai 官网源码

超级漂亮的 AI 官网源码 效果图部分源码领取源码下期更新预报 效果图 部分源码 <script lang"js">function project3D(x, y, z, vars) {var p, dx - vars.camXy - vars.camY - 8z - vars.camZp Math.atan2(x, z)d Math.sqrt(x * x z * z)x Math.sin(p - v…

List/ConcurrentBag Contains的效率比Dictionary/ConcurrentDictionary ContainsKey 低几千倍

测试目标&#xff1a; 用两组 50000 个字符串做包含(Contains)判断&#xff0c;分析性能差异。 50000个字符串集合测试结果&#xff1a; Dictionary/ConcurrentDictionary 能在1-2毫秒完成50000个字符串集合的包含判断&#xff1b; List 需要3秒&#xff0c;ConcurrentBag需要…

白话机器学习4:小波分解的原理与Python代码实现

小波去噪可以想象成使用一把“筛子”来过滤信号。这个“筛子”能够根据信号的不同频率成分&#xff0c;将其分解成多个层次。在这个过程中&#xff0c;信号的重要信息通常包含在低频部分&#xff0c;而噪声则多分布在高频部分。 将信号通过这个“筛子”分解后&#xff0c;我们可…

leetcode-最长公共子序列(二)-103

题目要求 思路 step 1&#xff1a;优先检查特殊情况。 step 2&#xff1a;获取最长公共子序列的长度可以使用动态规划&#xff0c;我们以dp[i][j]dp[i][j]dp[i][j]表示在s1中以iii结尾&#xff0c;s2中以jjj结尾的字符串的最长公共子序列长度。 step 3&#xff1a;遍历两个字…

同为科技详解智能PDU所应用的通信协议与接口

现如今&#xff0c;信息服务、AI人工智能的飞速发展与增长&#xff0c;全球正经历信息数据的爆炸。不仅数据量以惊人的速度增长&#xff0c;而且全球社会各行业对数据的依赖的程度也在日益增加。这些趋势使数据中心在全球都享有关键基础架构的地位。假设某个数据中心发生严重的…

软删除和硬删除的区别及实际应用

在处理数据存储时&#xff0c;删除操作是不可避免的一部分。然而&#xff0c;并非所有的删除都是相同的。在本篇博客中&#xff0c;我们将探讨软删除&#xff08;Soft Delete&#xff09;和硬删除&#xff08;Hard Delete&#xff09;的概念、它们之间的区别&#xff0c;以及如…

Docker安装Jenkins踩坑

问题一 Jenkins中没有root权限报错 解决办法 创建容器时候添加参数-uroot docker run -d -p 18080:8080 -p 50000:50000 -uroot -v /var/run/docker.sock:/var/run/docker.sock -v /data/jenkins_home:/var/jenkins_home -v /etc/localtime:/etc/localtime --name jenkins …

数据采集为什么会用到代理IP?

在数据采集中&#xff0c;代理IP是指通过使用代理服务器来隐藏或更改真实的IP地址&#xff0c;以访问目标网站或服务器。那么&#xff0c;数据采集为什么会用到代理IP呢&#xff1f;使用代理IP通常用于匿名地访问网站、绕过访问限制或提高数据采集的效率和安全性。 代理服务器作…

rk3399+qt实现IPC摄像头监看系统

需求 使用rk3399实现对nvr的摄像头进行查看 支持单/4分屏 自动轮询显示 与各个摄像头进行对讲联动&#xff0c;录像 方案 芯片平台&#xff1a;rk3399 桌面系统&#xff1a;ubuntu 界面开发&#xff1a;QT rtsp客户端&#xff1a;vlc/gstreamer 实现 主界面 未完待…