【Linux】探索进程控制奥秘,解锁高效实战技巧

news2024/11/18 5:52:07

目录

1.进程创建

1.1字符串常量为什么不可以修改?

1.2代码段和数据段到底是什么?

1.3.fork函数初识

1.4.fork函数返回值

1.5.写时拷贝:

1.6写时拷贝按需进行的原理(与页表的权限有关)

1.7.fork常规用法

2.进程终止

2.1.进程退出场景

2.2.进程常见退出方法

2.3.exit()函数和_exit()函数辨析

2.4.辨析退出码、错误码、退出信号

2.4.1退出码转换为错误码的操作

2.5.普通函数的返回值

3.进程等待

3.1.进程等待必要性

3.2wait()和waitpid()函数

wait

功能:

waitpid

功能:

返回值:

3.3阻塞等待和非阻塞等待

3.3.1、阻塞等待

3.3.2非阻塞等待

3.4通过位操作获取子进程的退出码和退出信号

4. 进程程序替换

4.1. 概念与原理

概念:

原理:

4.2. exec*系列替换函数

函数解释

命名理解

4.3那我们具体如何进行进程替换呢?

4.4替换为什么没有影响父进程?

1.进程创建

1.1字符串常量为什么不可以修改?

这里为什么编译不通过?

因为字符串具有常量属性,字符常量不可被修改。这里的问题是字符串为什么会有常量属性呢?

这里是字符串常量,具有常性,所以存储在了代码段当中的常量区。

因为这里的字符串地址一定是虚拟地址,而改成字符H,是在物理空间上做修改,所以此时就需要页表进行映射,而这里就会有权限的限制,只读的权限,不可修改,所以才会有字符常量不可修改这样的限制,本质是操作系统的锅!

1.2代码段和数据段到底是什么?

代码段里面存储的是可执行代码和常量区;数据段存储的是全局变量和静态变量

1.3.fork函数初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);
返回值:自进程中返回0,父进程返回子进程id,出错返回-1

1.4.fork函数返回值

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

1.5.写时拷贝:

为什么要用拷贝的形式,父进程直接将资源给子进程不就行了吗?

我们通常的操作有增删改查,可能会直接修改了原来的内容,所以需要额外拷贝一份资源。

通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式拷贝副本。

1.6写时拷贝按需进行的原理(与页表的权限有关)

在进行拷贝的时候,会将数据段的页表权限改成只读权限!然后任何一方想要进行写入的时候,这个时候操作系统就会介入,将权限改回来可读可写,所以当我们的子进程进行写入的时候就会报错缺页中断。操作系统就会介入,这样就写时拷贝就可以按需进行

页表不仅仅有将虚拟地址转换为物理内存,还会有权限位

1.7.fork常规用法

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

2.进程终止

2.1.进程退出场景

  • 进程代码运行完毕,结果正确
  • 进程代码运行完毕,结果不正确
  • 进程代码没用执行完,进程出异常了

2.2.进程常见退出方法

正常终止(可以通过 echo $? 查看进程退出码):
1. 从main返回
2. 调用exit
3. _exit
异常退出:
ctrl + c,信号终止

2.3.exit()函数和_exit()函数辨析

exit函数会支持刷新缓冲区,_exit函数不支持。exit()底层封装了_exit(),两者是上下层关系

2.4.辨析退出码、错误码、退出信号

退出码包含错误码,当退出码是0的时候,表示程序正常退出;如果退出码!=0,这个退出码就表示错误码。然后利用sterror函数将其进行转换。
进程如果在执行的时候异常了,os会发送信号终止它,这个就是退出信号。非0就代表程序出异常,0代表程序正常执行。
任何进程最终的执行情况,我们可以使用两个数字表明具体的执行情况,一个是退出码,另一个就是退出信号

2.4.1退出码转换为错误码的操作

使用语言或者系统自带的方法进行转化,例如:在linux中,使用strerror()函数。

char* strerror(int errnum);

#include<stdio.h>
#include<string.h>                                                                                                                             
int main()
{
    for(int i = 0; i < 100; i++)
        printf("%d:%s\n", i, strerror(i));
    
    return 0;
}

2.5.普通函数的返回值

  • 普通函数退出,仅仅表示函数调用完毕。
  • 函数也被称为子程序,与进程退出时返回退出码类似,函数执行完毕也会返回一个值,这个值通常用于表示函数的执行结果或状态。
  • 调用函数,我们通常想看到两种结果:a.函数的执行结果(函数的返回值);b.函数的执行情况(函数是否成功执行了预期的任务),例如:fopen()函数的执行情况是通过其执行结果来间接表示。

fopen函数举例:返回了非空的FILE*指针,则可认为函数执行成功;返回了NULL,则可认为函数执行失败,需要进一步检查错误的原因(errno变量或调用perror()函数)。

3.进程等待

3.1.进程等待必要性

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

3.2wait()和waitpid()函数

wait

pid_t wait(int* status);

功能:

等待任意一个子进程结束,并回收其资源。
返回值:调用成功,返回已经结束进程的PID,同时获取到了子进程的退出状态码;调用失败,返回-1,并设置错误码以指示错误的原因。

参数status:输出型参数,用于存储子进程的退出状态,由OS填充,如果不需要这个信息,可以传递NULL,否则,OS会根据该参数,将子进程的信息反馈给父进程。

waitpid

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

功能:

等待任意一个子进程或者指定的子进程结束,并回收其资源。
参数pid:如果pid = -1,等待任意一个子进程,与wait等效;如果pid > 0,等待其进程的PID与pid相等的子进程。

参数option:如果option = 0,则为阻塞等待;如果option = WNOHANG,则为非阻塞等待。

返回值:

调用成功,返回收集到的子进程的PID,同时获取到了子进程的退出状态码;调用失败,返回-1,并设置错误码以指示错误的原因;如果为非阻塞等待,waitpid调用成功且没有收集到已结束的子进程,则返回0。

3.3阻塞等待和非阻塞等待

3.3.1、阻塞等待

定义:进程在发出某个请求(如:I/O操作、等待某个条件成立等)后,如果请求不能立即得到满足(如:数据未准备好、资源被占用等),进程会被挂起,在此期间无法继续执行其他任务,直到等待条件满足或被唤醒
一心一意,专心做一件事!

特点:
a.行为 -> 进程在等待期间无法执行其他任务。

b.触发方式 -> 等待由外部条件触发(如:数据到达、资源释放等)。

c.管理层面:由操作系统或者底层系统资源管理。

d.效率与并发性:效率低。

应用场景:实时性要求不高,等待时间相对比较短的情况,如:简单文件的读写操作。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
      
    if(id == 0) //子进程
    {
        int cnt = 5;
        while(cnt)
        {
            printf("child is running, id:%d, ppid:%d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1); //子进程退出
    }
  
    int status = 0; //存储子进程退出状态
    pid_t rid = waitpid(id, &status, 0); //父进程等待 —— 阻塞等待
    if(rid > 0) //等待成功
        printf("wait success, status:%d\n", status);
    else if(rid == -1) //调用失败
        perror("wait error!\n");                                     
    
    return 0;
}

3.3.2非阻塞等待

定义:进程在发出某个请求后,不会被立即挂起已等待请求的完成,即使请求不能立即得到满足,进程在等待期间可以继续执行其他任务,同时可能会以某种方式(轮询访问、回调等)定期检查请求状态或者等待结果的通知。

特点:
a.行为 -> 进程在等待期间可以执行其他任务;

b.触发方式 -> 可能通过编程的方式实现,如:轮询、回调等。

c.管理层面:在应用层通过编程实现。

d.效率与并发性:效率高,提高并发性和响应能力。

应用场景:需要高并发和响应能力的场景,如:在网络编程中,服务器同时处理多个客户端的请求。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
 
#define SIZE 5 

int main()
{
    Inittask();
    pid_t id = fork(); 
    if(id == 0) //子进程
    {
        int cnt = 2;
        while(cnt)
        {
            printf("I am a process, id:%d, ppid:%d\n", getpid(), getppid());             			sleep(1);
         cnt--;
         }
         exit(1); //子进程退出
      }
  
      int status = 0; //存储子进程退出状态
      while(1) //基于非阻塞轮询的访问
      {
        pid_t rid = waitpid(id, &status, WNOHANG); //非阻塞等待
        if(rid > 0) //调用成功,收集到了已经结束的子进程                                            {
          printf("wait success, status:%d\n", status);
          break;
        }
        else if(rid == 0) //调用成功,未收集到已经结束的子进程
        {
            printf("child is running, father do other thing!\n");
            printf("------------ Task begin ----------------\n");
            executeTask(); //等待期间,执行其他任务 
            printf("------------ Task end ----------------\n");
        }
        else //调用失败
        {
            perror("wait error\n");
            break;
        }
          
        sleep(1);
    }
    return 0;
}

3.4通过位操作获取子进程的退出码和退出信号

我们这里只讲解16位的情况下

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

int main()
{
    pid_t id = fork();
      
    if(id == 0) //子进程
    {
        int cnt = 5;
        while(cnt)
        {
            printf("child is running, id:%d, ppid:%d\n", getpid(), getppid());
            sleep(1);
            cnt--;
        }
        exit(1); //子进程退出
    }
  
    int status = 0; //存储子进程退出状态
    pid_t rid = waitpid(id, &status, 0); 
    if(rid > 0) //等待成功
        printf("wait success, status:%d, exit code:%d, exit sign:%d\n", 
status, (status>>8)&0xff, status&0x7f);   //位操作获取子进程的退出码、退出信号        
 
     return 0;
}

4. 进程程序替换

4.1. 概念与原理

概念:

它允许一个进程在执行期间,用一个新的程序来替换当前正常执行的程序,即:用全新的程序替换原有的程序。
这意味着进程在调用一种exec函数,当前进程的用户空间代码和数据被新程序的代码和数据完全替换(覆盖),从新程序的启动例程开始执行。

注意:调用exec函数,并不会创建新的进程,而是对原有进程的资源进行替换,因此调用exec前后该进程的pid并未发生改变

原理:

加载新程序 -> 替换当前程序 -> 更新页表 -> 执行新程序。

  • 加载新程序:当进程决定进行程序替换时(调用exec函数),它会请求OS将全新程序(代码和数据)从磁盘中加载到内存。
  • 更新页表:为了实现替换,OS需要更新页表,将原来指向旧程序代码的虚拟地址映射到新程序代码的物理地址上,这样,就会执行新程序的代码。

4.2. exec*系列替换函数

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

#include <unistd.h>`
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[]);

函数解释

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

命名理解

这些函数原型看起来很容易混,但只要掌握了规律就很好记。
l(list) : 表示参数采用列表
v(vector) : 参数用数组
p(path) : 有p自动搜索环境变量PATH
e(env) : 表示自己维护环境变量

事实上,只有execve是真正的系统调用,其它五个函数最终都调用 execve,所以execve在man手册 第2节,其它函数在man手册第3节。这些函数之间的关系如下图所示

4.3那我们具体如何进行进程替换呢?

我们要知道当我们把一个程序进行./,那么这个程序就变成了一个进程,而在我们的这个进程中执行了关于进程替换的函数,那么该进程就会被替换,执行另一个进程!

我们不一定要让一个进程直接进行替换,可以创建子进程,让子进程进行替换,让父进程等待我们的结果就可以.

4.4替换为什么没有影响父进程?

因为进程具有独立性,我们将子进程进行替换,发生写时拷贝,不会影响父进程

一次想生成两个可执行文件,就需要这么写,不然makefile默认值生成第一条指令!

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

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

相关文章

跟着iMeta学做图 | 冲击图展示菌群随盐度的变化

本文代码已经上传至https://github.com/iMetaScience/iMetaPlot如果你使用本代码&#xff0c;请引用&#xff1a;Changchao Li. 2023. Destabilized microbial networks with distinct performances of abundant and rare biospheres in maintaining networks under increasing…

gen_server补充基础学习

学习gen_server的回调结构 gen_server:start_link(Name, Mod, InitArgs, Opts)这个调用是所有事物的起点。它 会创建一个名为Name的通用服务器&#xff0c;回调模块是Mod&#xff0c;Opts则控制通用服务器的行为。在这里可以指定消息记录、函数调试和其他行为。通用服务器通过…

基于QGIS 3.16.0 的OSM路网矢量范围裁剪实战-以湖南省为例

目录 前言 一、相关数据介绍 1、OMS路网数据 2、路网数据 3、路网图层属性 二、按省域范围进行路网裁剪 1、裁剪范围制定 2、空间裁剪 3、裁剪结果 三、总结 前言 改革开放特别是党的十八大以来&#xff0c;我国公路发展取得了举世瞩目的成就。国家高速公路网由“7 射…

ATECLOUD平台相比传统ATE测试有哪些独特的优势?

随着科技的飞速发展&#xff0c;在电子测量行业&#xff0c;自动化测试也逐渐取代了传统手动&#xff0c;市场上的大多数测试企业近几年都在进行自动化转型&#xff0c;而伴随着测试行业自动化、智能化的趋势&#xff0c;各类自动化测试系统也发展迅速&#xff0c;在众多ATE自动…

一种模式包含引流和复购 让你的私域电商平台腾飞!

在当今的商业环境中&#xff0c;一种名为“循环购”的创新商业模式正悄然兴起&#xff0c;它打破了传统消费观念&#xff0c;让“消费1000送2000&#xff0c;每日领钱&#xff0c;轻松提现”不再是遥不可及的梦想。很多人可能会问&#xff0c;这究竟是商家的慷慨解囊&#xff0…

多模态大模型-MiniCPM-V

1. 简介 本文主要探索如何在性能和效果之间的权衡&#xff0c;希望能在合适的性能下&#xff0c;模型效果有大幅的提升。主要贡献点有&#xff1a; 通过模型结构&#xff0c;数据&#xff0c;训练策略等优化&#xff0c;让MiniCPM-Lllama3-V 2.5[1]在OpenCompass评测上超过了…

03:手动可变电阻

可变电阻 1、电位器2、变阻器/数字电位器2.1&#xff1a;变阻器2.2&#xff1a;数字电位器 3、电位器锥度4、寄生电感/电容 1、电位器 如上图所示&#xff1a;将可变的电阻作为分压器&#xff0c;那么这种可变的电阻就是电位器。例如&#xff1a;将L1连接负极&#xff0c;L3连接…

SQLMAP windows10 安装记录

1.Windows系统下安装Sqlmap&#xff0c;需要安装python环境 python下载和安装 官方下载 https://www.python.org/downloads 建议直接下载安装版&#xff1a;installer 最后到这个界面正常是成功了&#xff0c;但也要校验一下 进入windows 命令窗口 正常输入你安装的python 版本…

OpenHarmony(鸿蒙南向开发)——轻量系统芯片移植指南(二)

往期知识点记录&#xff1a; 鸿蒙&#xff08;HarmonyOS&#xff09;应用层开发&#xff08;北向&#xff09;知识点汇总 鸿蒙&#xff08;OpenHarmony&#xff09;南向开发保姆级知识点汇总~ OpenHarmony&#xff08;鸿蒙南向开发&#xff09;——轻量系统芯片移植指南(一) Op…

数据库与表的操作

目录 1. 库的操作 1.1创建数据库案例 1.2字符集和校验规则 1.2.1查看系统默认字符集以及校验规则 1.2.2 查看数据库支持的字符集 1.2.3查看数据库支持的字符集校验规则 1.2.4校验规则对数据库的影响 1.3.1查看数据库 1.3.2显示创建语句 1.3.3修改数据库 1.3.4 数据…

将一句英文颠倒输出

例如&#xff1a; 输入&#xff1a;s “i am from beijing" 输出&#xff1a;”beijing form am i“ #include <stdio.h> #include <string.h>//i am form nanjing //ginjnan mrof ma i //nanjing form am i//逆序算法(首尾互换) void Reserve(char *s,in…

windows 创建新用户,并分配到指定组

右击电脑 -> 点击管理 在右边右击&#xff0c;选择新用户&#xff0c;输入相关信息创建 创建用户后&#xff0c;选择用户&#xff0c;右击&#xff0c;选择属性&#xff0c;添加 点击高级 点击立即查找&#xff0c;可以搜索出所有可用的组&#xff0c;为其选择即可

mosh java 2.4 inheritance继承

1.面向对象编程的第三个特点 继承 继承的好处 java不能继承多个父级 代码文件结构 1.main.java package org.example; //package org.codewithme;//import org.example.UIControl;//TIP To <b>Run</b> code, press <shortcut actionId"Run"/> or /…

《深度学习》PyTorch 手写数字识别 案例解析及实现 <上>

目录 一、了解MINIST数据集 1、什么是MINIST 2、查看MINIST由来 二、实操代码 1、下载训练数据集 2、下载测试数据集 运行结果&#xff1a; 3、展示手写数字图片 运行结果&#xff1a; 4、打包图片 运行结果&#xff1a; 5、判断当前pytorch使用的设备 1&#xff…

RK3568 外接摄像头预览方向错误

1.测试发现摄像头预览方向被旋转了90度 2.问题原因: device\rockchip\common\external_camera_config.xml 配置文件旋转了90度: 3.解决对策 修改orientation为0度

MultiSnapRecyclerView:让Android RecyclerView的滚动停靠更灵活

在Android应用开发中&#xff0c;RecyclerView是一个强大且灵活的组件&#xff0c;用于展示大量数据集合。然而&#xff0c;标准的RecyclerView只支持单一的滚动停靠点&#xff0c;这在某些场景下可能不够灵活。为了解决这个问题&#xff0c;TakuSemba开发了一个名为MultiSnapR…

Vue3+TS项目给el-button统一封装一个点击后转圈效果的钩子函数按钮防抖

前言 每个按钮都要单独定义一个loading变量&#xff0c;并且在接口请求前修改为true&#xff0c;接口响应后再修改为false&#xff0c;封装后这段重复的逻辑就可以统一管理不用每次都写一遍了。 效果 新建一个公共的src\common.ts import { ref } from "vue"expor…

Apache-wed服务器环境的安装

一。安装httpd并且开启httpd yum install httpd systemctl start httpd 二。关闭防火墙 systemctl stop firewall 三。常规配置wed服务 mkdir /www vim index.html&#xff08;里面写入自己的内容&#xff09; chmod 755 index.htm chmod 755 /www vim /etc/httpd/co…

管家婆云辉煌手机端怎么连接蓝牙打印机?

管家婆云辉煌手机端可以连接蓝牙打印机&#xff0c;这样手机可以发送打印任务到蓝牙打印机&#xff0c;完成打印任务。具体的设置步骤如下&#xff1a; 一、首先完成手机和蓝牙打印机配对&#xff0c;打开蓝牙打印机后。手机开启蓝牙和定位服务 点击手机设置&#xff0c;进入手…

价值流案例研究:实战经验与成功实践的深度解析

价值流在实际应用中的强大效益 在全球化和数字化竞争愈加激烈的背景下&#xff0c;企业正面临如何优化内部流程、提升客户体验、减少成本和提高效益的多重挑战。《价值流指南》不仅是一种理论工具&#xff0c;更为企业提供了系统化的实践框架&#xff0c;以实现从理论到实战的…