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){ //child
        g_val = 100;
        printf("child[%d]: %d : %p\n", getpid(), g_val, &g_val);
    }
    else{ //parent
        printf("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 )
    { //child
        printf("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");
    // 只要是个函数,掉用就有可能失败 就是没有替换成功 继续执行下面的原代码
    // 只有错误时会返回,返回值为-1
    execl("/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;
}

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

差异基因散点图绘制教程

差异基因散点图绘制教程 本期教程 小杜的生信笔记&#xff0c;自2021年11月开始做的知识分享&#xff0c;主要内容是R语言绘图教程、转录组上游分析、转录组下游分析等内容。凡事在社群同学&#xff0c;可免费获得自2021年11月份至今全部教程&#xff0c;教程配备事例数据和相…

简单有效的数据加密方法你知道几个?

1. 文件和邮件加密 利用华企盾DSC数据防泄密系统&#xff0c;企业可以实现文件和邮件的加密。系统提供了一键式的文件加密解决方案&#xff0c;确保敏感信息在电子邮件中传输时得到安全保护&#xff0c;即使邮件被截获&#xff0c;内容也无法被未授权人员阅读。 2. 端到端数据…

Ubuntu系统如何使用宝塔面板搭建HYBBS论坛并发布公网远程访问

文章目录 前言1. HYBBS网站搭建1.1 HYBBS网站安装1.2 HYBBS网站测试1.3. cpolar的安装和注册 2. 本地网页发布2.1.Cpolar临时数据隧道2.2.Cpolar稳定隧道&#xff08;云端设置&#xff09;2.3.Cpolar稳定隧道&#xff08;本地设置&#xff09; 3.公网访问测试总结 前言 在国内…

Redis教程——哨兵

在上篇文章我们学习了Redis教程——主从复制&#xff0c;这篇文章我们学习Redis教程——哨兵监控。 在主从复制中如果主机发生宕机&#xff0c;从机Redis会一直等到主机的恢复&#xff0c;这样会导致只能进行读操作&#xff0c;不能进行写操作&#xff0c;这大大降低了系统的高…

flexible.js+rem页面适配

简介 flexible.js 介绍 flexible.js 是一个用于移动端页面适配的 JavaScript 库&#xff0c;由阿里巴巴团队开发并开源。在移动 web 开发中&#xff0c;由于设备屏幕尺寸、分辨率以及像素比的差异&#xff0c;开发者通常需要编写额外的代码来确保页面在不同设备上都能正确显示…

【WEEK12】 【DAY1】整合JDBC【中文版】

2024.5.13 Monday 目录 11.整合JDBC11.1.SpringData简介11.2.新建springboot-04-data项目11.3.新建application.yaml11.4.连接数据库11.5.修改Springboot04DataApplicationTests.java11.5.1.查看DataSourceProperties.java和DataSourceAutoConfiguration.java 11.6.JDBCTempla…

在CentOS 7服务器及Windows 10客户端间建立并配置NFS服务

在CentOS 7服务器及Windows 10客户端间建立并配置NFS服务 引言 网络文件系统(Network File System)&#xff0c;简称NFS&#xff0c;是一种分布式文件系统协议。它允许网络上的客户端机器像访问本地磁盘文件一样&#xff0c;通过网络访问服务器上的文件。在某些特定的业务场景中…

机器学习材料性能预测与材料基因工程应用实战

传统的材料研发技术是通过实验合成表征对材料进行试错和验证&#xff0c;而过去的计算手段受限于算法效率&#xff0c;无法有效求解实际工业生产中面临的复杂问题。 近几年随着大数据和人工智能介入&#xff0c;通过采用支持向量机、神经网络等机器学习算法训练数据集来构建模…

【JAVA】BOSS系统发版艺术:构建高效、优雅的微服务部署策略

在现代软件开发领域&#xff0c;微服务架构与容器化部署已迅速成为行业新趋势。微服务架构通过将应用拆分成多个小型、自治的服务单元&#xff0c;每个服务承担某项特定的业务功能。而容器化部署则以其轻量级和高度可移植的特性&#xff0c;为这些微服务的有效打包、分发和运行…

ApiHug 官方

&#x1f917; ApiHug {Postman|Swagger|Api...} 快↑ 准√ 省↓ GitHub - apihug/apihug.com: All abou the Apihug apihug.com: 有爱&#xff0c;有温度&#xff0c;有质量&#xff0c;有信任ApiHug - API design Copilot - IntelliJ IDEs Plugin | MarketplaceApiHug-H…

java版数据结构:二叉树

引言&#xff1a;什么是树 树是一种非线性的数据结构&#xff0c;由节点组成&#xff0c;节点之间以边连接。树结构中最重要的特点是&#xff0c;每个节点可以有多个子节点&#xff0c;但每个节点只有一个父节点&#xff08;除了根节点&#xff09;。树结构中没有环路&#xff…

水雨情监测系统—实时监测水位信息

TH-SW3水雨情监测系统是一种专门用于实时监测和收集水文气象数据的自动化系统。它能够实时获取区域内降雨和水情数据&#xff0c;并将其存储到数据库中进行分析处理&#xff0c;从而为防汛指挥人员提供及时准确的信息服务。 水雨情监测系统的主要功能包括实时监测水位、流速、流…

第十一届蓝桥杯大赛软件类决赛 Java A 组

文章目录 发现宝藏【考生须知】试题 A: 合数个数试题 B : 含 2 天数试题 C: 本质上升序列试题 D: 迨尺天涯试题 E: 玩具蛇试题 F: 游园安排试题 G: 画廊试题 H: 奇偶覆盖试题 I: 补给试题 J: 蓝跳跳 发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&…

基于C++基础知识的指针

一、变量与指针 在C中&#xff0c;变量是用来存储数据的一个标识符&#xff0c;而指针是一个变量&#xff0c;该变量存储的是另一个变量的地址。 变量可以是不同的数据类型&#xff0c;包括整数&#xff08;int&#xff09;、浮点数&#xff08;float&#xff09;、字符&#…

deveco studio 打开官方案例,不显示运行按钮。

就拿官方的search举例好了 git 地址 https://gitee.com/harmonyos/samples/tree/master/ETSUI/Search 使用deveco studio打开Search项目&#xff0c;打开Tools->Device-Manager中的Local Emulator本地模拟器&#xff0c; 此时会发现&#xff0c;运行按钮是灰色的&#xff0…

OVZ虚拟化:解锁高性能的虚拟化利器

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 OVZ虚拟化&#xff1a;解锁高性能的虚拟化利器 前言OVZ虚拟化简介OVZ虚拟化的优势OVZ虚拟化的应用场景OVZ虚拟化的部署与管理 前言 在当今快节奏的数字时代&#xff0c;虚拟化技术是推动云计算和容器…

通用人工智能将如何重塑未来

通用人工智能(AGI)是一种人工智能&#xff0c;具有与人类一样的获取知识、应用知识解决问题和理解能力。与专门处理受限任务的狭义人工智能系统不同&#xff0c;AGI寻求发展先进的认知技能&#xff0c;以促进在不同情况下完成复杂任务。AGI是一种人工智能&#xff0c;试图模仿人…

下载源代码并交叉编译riscv FreeBSD系统和内核

RISCV系统曾经让人神秘到无法接触&#xff0c;交叉编译更是只有耳闻&#xff0c;现在随着RISCV的普及&#xff0c;它们神秘的面纱已经被慢慢揭开。 交叉编译作为RISCV系统中的一个重要环节&#xff0c;也随着RISCV的普及而变得更加容易理解和操作。交叉编译允许开发者在一个平…

部署达梦数据库主从配置详细操作DM8

服务器配置 主库 192.168.81.128 实例名 dm-1 从库 192.168.81.129 实例名 dm-2 以下安装部署主从服务器都操作 关闭防火墙 systemctl stop firewalld && systemctl disable firewalld 注意安装前必须创建 dmdba 用户&#xff0c;禁止使用 root 用户安装数据库。…

下载element-ui报错

此错误表示尝试从npm注册表下载“resize observer polyfill”包时超时。这可能是由于网络连接问题或npm注册表服务器的问题。 要解决此问题&#xff0c;您可以尝试以下步骤&#xff1a; 1.重试npm install命令&#xff1a;有时&#xff0c;网络问题会导致临时超时。再次运行npm…