Linux C语言 22-多进程

Linux C语言 22-进程

本节关键字:进程、exec函数族
相关C库函数:fork、getpid、getppid、getuid、geteuid、getgid、getegid、execl、execlp、execv、execvp、execle、execvpe

什么是进程?

  • 进程是程序的执行过程;
  • 进程是动态的,随着程序的使用被创建,随着程序的结束而消亡;
  • 进程是系统调度的独立任务;
  • 进程是程序执行的独立任务;
  • 进程是内存资源管理的最小任务。
    注意:一个程序可以只有一个进程,也可以有多个进程(程序由多个进程动态执行);每一个程序运行时,操作系统分配给进程的是虚拟内存,意味着每一个进程所使用的空间都是虚拟内存, 虚拟内存会被单元管理模块(MMU)映射到物理内存上,如何映射是操作系统关心的事情,程序开发者不用关心。

C程序的启动和终止:
C程序的启动和终止

时间片

进程有多个,而CPU只有一个,假设该CPU是单核的,那么在某一时刻CPU只能处理一个进程,但是不能一直去处理这个进程,得多个进程之间轮流处理,给用户感觉这些进程在同时进行,而CPU处理一个进程的时间段即时间片。时间片是约定好CPU处理一个进程的时间段。

进程的类型

  • 交互进程:完成人机交互的进程,可以在前台运行,也可以在后台运行。
  • 批处理进程:与终端无关,被提交到一个作业队列中顺序执行。
  • 守护进程:和终端无关,一直到后台运行。

进程的状态

  • 运行态:正在占用CPU执行任务。
  • 等待态:又称阻塞态或睡眠态,缺少某些资源而让出CPU。
  • 就绪态:资源准备就绪,等待CPU调度。
    进程状态转化

进程的模式

  • 终端:内核发送的信号。
  • 系统调用:调用操作系统提供的访问硬件的一组接口。

特殊进程

特殊进程是指处于一种非常规状态的进程,在这里主要将其分为孤儿进程和僵尸进程。

孤儿进程

父进程比子进程先退出的进程称为孤儿进程,孤儿进程会被进程号为1的init进程收养。

僵尸进程

子进程比父进程先退出,但没有被父进程回收资源的进程称为僵尸进程,僵尸进程会造成空间浪费和资源泄漏等问题。

进程的状态标志

  • D 不可中断的静止
  • R 正在执行中
  • S 阻塞状态
  • T 暂停执行
  • Z 不存在但暂时无法消除
  • < 高优先级的进程
  • N 低优先级的进程
  • L 有内存分页分配并锁在内存中

父进程和子进程之间的关系

父进程和子进程之间对打开文件的共享
父进程和子进程之间对打开文件的共享

除了打开文件之外,父进程的很多其他属性也由子进程继承,包括:

  • 实际用户ID、实际组ID、有效用户ID、有效组ID
  • 附属组ID
  • 进程组ID
  • 会话ID
  • 控制终端
  • 设置用户ID标志和设置组ID标志
  • 当前工作目录
  • 根目录
  • 文件模式创建屏蔽字
  • 信号屏蔽和安排
  • 对任一打开文件描述符的执行时关闭(close-cm-exec)标志
  • 环境
  • 连接的共享存储段
  • 存储映像
  • 资源限制

父进程和子进程之间的区别具体如下:

  • fork的返回值不同。
  • 进程ID不同。
  • 这两个进程的父进程ID不同:子进程的父进程ID是创建它的进程的ID,而父进程的父进程ID则不变。
  • 子进程的 tms_utime、tms_stime、tms_cutime 和 tms_ustime 的值设置为 0
  • 子进程不继承父进程设置的文件锁。
  • 子进程的未处理闹钟被清除。
  • 子进程的未处理信号集设置为空集。

进程相关库函数

#include <unistd.h>
// 通过复制调用进程来创建一个新进程,子进程返回0,父进程返回子进程ID,出错时父进程返回-1,并设置error为错误码
// 复制的子进程是从父进程fork()调用后面的语句开始执行的
// EAGIN 无法分配足够的内存来复制父级的页表并为子级分配任务结构
// ENOMEM 由于内存紧张,fork()无法分配必要的内核结构
// SENOSYS 此平台不支持fork()(例如,没有内存管理单元的硬件)
// ERESTARTNOINTR 系统调用被信号中断,将重新启动。(这只能在跟踪过程中看到
pid_t fork(void);

// 进程标识
// 获取当前进程的ID
pid_t getpid(void);
// 获取当前进程的父进程的ID
pid_t getppid(void);
// 获取当前进程实际用户ID
uid_t getuid(void);
// 获取当前进程有效用户ID
uid_t geteuid(void);
// 获取当前进程使用用户组ID
gid_t getgid();
// 获取当前进程有效用户组ID
gid_t getegid();

// 进程退出 将status传递给父进程
#include <stdlib.h>
void exit(int status);

// 进程回收 
#include <sys/wait.h>
// 阻塞等待进程号为*stat_loc的进程退出
pid_t wait(int *stat_loc);

// 等待子进程退出
// 如果pid == (pid_t)-1,options为0,则waitpid函数等效于wait函数
// 如果pid == (pid_t)-1,则会请求任何子进程的状态
// 如果pid > 0,则指定请求状态的单个子进程的进程ID
// 如果pid == 0,则会为进程组ID等于调用进程的进程组ID的任何子进程请求状态
// 如果pid < (pid_t)-1,则会为进程组ID等于pid绝对值的任何子进程请求状态
// WCONTINUED 报告pid指定的任何连续子进程的状态,该进程的状态自作业控制停止后一直没有报告
// WNOHANG 如果pid指定的某个子进程的状态不立即可用,则waitpid函数不应暂停调用线程的执行
// WUNTRACED pid指定的任何已停止的子进程的状态,以及自停止以来尚未报告其状态的子进程,也应报告给请求进程
// 如果调用进程设置了SA_NOCLDWAIT或SIGCHLD设置为SIG_IGN,并且该进程对于转换为僵尸进程的子进程没有未经访问的权限,则调用线程应阻止,直到包含调用线程的进程的所有子进程终止,wait()和waitpid()将失败并将errno设置为[ECHILD]
pid_t waitpid(pid_t pid, int *stat_loc, int options);

进程相关库函数使用示例

#include <stdio.h>
#include <unistd.h>

void test01(void)
{
    printf("======= main process begin =======\n");
    
    int res = 10;
    pid_t pid;
    
    pid = fork();
    if (pid == -1)
    {
        perror("fork error");
        return;
    }
    if (pid == 0) // 子进程
    {
        printf("i am child: %d, my parent: %d\n", getpid(), getppid());
        printf("i am child: uid[%d] euid[%d] gid[%d] egid[%d]\n", 
            getuid(), geteuid(), getgid(), getegid());
        
        while (res <= 20)
        {
            sleep(2);
            
            res += 1;
            printf("child check res: %d\n", res);
        }
    }
    if (pid > 0) // 父进程
    {
        printf("i am main: %d, my child: %d, my parent: %d\n", getpid(), pid, getppid());
        printf("i am main: uid[%d] euid[%d] gid[%d] egid[%d]\n", 
            getuid(), geteuid(), getgid(), getegid());
        while (res >= 0)
        {
            sleep(1);
            
            res -= 2;
            printf("main check res: %d\n", res);
        }
        
        printf("i am main, i am waiting child\n");
        wait(&pid); // 防止僵尸进程的出现
        printf("i am main, i will exit\n");
    }
    printf("======= main process end =======\n");
}

void test02(void)
{
    printf("======= main process begin =======\n");
    pid_t pid;
    
    pid = fork();
    if (pid == -1)
    {
        perror("fork error");
        return;
    }
    if (pid == 0) // 子进程
    {
        printf("i am child: %d, my parent: %d\n", getpid(), getppid());
        
        while (1)
        {
            sleep(2);
        }
    }
    if (pid > 0) // 父进程
    {
        printf("i am main: %d, my child: %d, my parent: %d\n", getpid(), pid, getppid());
        
        while (1)
        {
            sleep(1);
        }
        
        printf("i am main, i am waiting child\n");
        wait(&pid); // 防止僵尸进程的出现
        printf("i am main, i will exit\n");
    }
    printf("======= main process end =======\n");
    
/**
孤儿进程的验证步骤:
ps -ajx 查询结果的表头:
 PPID   PID  PGID   SID TTY      TPGID STAT   UID   TIME COMMAND

1、验证编译出来的a.out程序运行了两个进程
    ps -ajx | grep a.out | grep -v grep
2、查看a.out的进程号 pid
    ps -ajx | grep a.out | grep -v grep
3、进一步筛选与pid相关的进程信息
    ps -ajx | grep pid | grep -v grep
4、通过pid的关系可以看出:
    a.out主进程的父进程是 -bash    
    a.out子进程的父进程是 a.out的主进程
5、先结束父进程,观察子进程的PPID变化(由pid变成了1,即init进程号)
    ps -ajx | grep a.out | grep -v grep
    kill 父进程PID
    ps -ajx | grep a.out | grep -v grep

补充:
    kill -l  查看所有信号
    kill PID 结束进程号为PID的进程
*/
}

void test03(void)
{
    // 本来是希望利用for循环创建5个进程,结果创建了32个
    // 问题解决:在当前进程为子进程时,不执行fork即可
    printf("======= main process begin =======\n");
    pid_t pid;
    int i;
    
    for (i=0; i<5; i++)
    {
        pid = fork();
        if (pid == -1)
        {
            perror("fork error");
            return;
        }
        if (pid == 0) // 子进程
        {
            printf("i am child: %d, my parent: %d\n", getpid(), getppid());
            // break; // 注释解开时创建5个进程,不解开时创建32个进程
        }
        if (pid > 0) // 父进程
        {
            printf("i am main: %d, my child: %d, my parent: %d\n", getpid(), pid, getppid());
        }
    }
    while (1)
    {
        sleep(1);
    }
    
    if (pid > 0)
    {
        printf("i am main, i am waiting child\n");
        wait(&pid); // 防止僵尸进程的出现
        printf("i am main, i will exit\n");
    }
    printf("======= main process end =======\n");
    
/**
查看程序创建的进程个数:
    ps -ajx | grep a.out | grep -v grep | wc -l
结束程序a.out
    pkill a.out
*/
}

int main(void)
{
    test01(); // 验证复制创建进程
    // test02(); // 验证孤儿进程
    // test03(); // 控制进程创建个数
    
    return 0;
}
/** 运行结果:
======= main process begin =======
i am main: 17688, my child: 17689, my parent: 10139
i am main: uid[1000] euid[1000] gid[1000] egid[1000]
i am child: 17689, my parent: 17688
i am child: uid[1000] euid[1000] gid[1000] egid[1000]
main check res: 8
main check res: 6
child check res: 11
main check res: 4
child check res: 12
main check res: 2
main check res: 0
child check res: 13
main check res: -2
i am main, i am waiting child
child check res: 14
child check res: 15
child check res: 16
child check res: 17
child check res: 18
child check res: 19
child check res: 20
child check res: 21
======= main process end =======
i am main, i will exit
======= main process end =======
*/

exec函数族

exec函数族用于进程程序替换,子进程执行的是父进程的代码片段,那么当我们想让创建出来的子进程执行全新的程序时怎么办呢?这个时候我们就需要使用进程的程序替换了。
那我们为什么要进行程序替换呢?其实也不难理解,大概可以分为两点:

  • 我们想让子进程执行一个全新的程序;
  • 完成不同语言编写的程序间可以互相调用。

一般在进行服务器设计(Linux编程)的时候,往往需要子进程干两类事情:

  • 子进程执行父进程的代码段(服务器代码)
  • 子进程执行磁盘中一个全新的程序(shell让客户端执行对应的程序,通过我们的进程去执行其他人写的进程代码等,编程语言可以由 C/C++ -》 C/C++/Python/Shell/Java …)
程序替换为什么使用子进程?

注意:进行程序替换的是子进程!!!原因如下:

  • 进程替换永远影响的是进程的本身,子进程的替换永远不会影响父进程,因为进程具有独立性;
  • 独立性体现在内核层面,不同进程有不同的地址空间,有不同的页表替换只是加入新的代码和数据;
  • 重新建立的是页表映射但并不影响内核数据结构的具体情况;
  • 子进程虽然和父进程代码共享数据写实拷贝,但是一旦发生进程替换了,就认为代码和数据发生了双写实拷贝,就彻底将两个进程分开了;
  • 引入子进程的原因就是,一方面把需求做到位,另一方面不影响父进程,因为父进程可能还要接收新的命令,再去执行新的程序。
exec函数族的六个进程替换函数 && system函数
// 根据PATH环境变量寻找待执行程序,成功不返回(因为去执行程序了),失败返回-1;因为只有失败才返回,错误值-1,所以通常我们直接在exec函数调用后直接调用perror(),和exit(),无需if判断
// 参数1 程序名
// 参数2 argv0
// 参数3 argv1
// ... argvN
// 最后 NULL
// 区别:execlp(),让当前进程或者子进程执行系统命令,比如:ls,cat,cp等命令,而execl()则是执行自己所有的可执行程序,比如一个c程序a.out
// 示例:execl("/bin/echo", "echo", "Hello World", NULL);
int execl(const char *path, const char *arg, ...);

// 执行程序file,成功返回0,失败返回-1
// 示例:execlp("ls", "ls", "-l", NULL);
int execlp(const char *file, const char *arg, ...);

// 与execlp()函数不同的是,execle()函数可以显式地指定新程序的环境变量数组
// 示例:char *env_init[] = {"XX=xx", "OO=oo", NULL}; execle("./echoenv", "echoenv", NULL, env_init);
int execle(const char *path, const char *arg, ..., char *const envp[]);

// 执行指定路径下可执行文件的函数。该函数会将当前进程替换为指定路径下的可执行文件,并传递给新程序一个参数列表
// path:表示要执行的可执行文件的路径名
// argv:参数列表,是一个字符串数组,其中每个元素都是一个参数。最后一个元素必须为 NULL,用于标记参数列表的结束 
// 示例:char *argv[]={"ls", NULL, NULL}; execv("/bin/ls", argv);
int execv(const char *path, char *const argv[]);

// 在系统的 PATH 环境变量指定的路径中搜索可执行文件,当调用execvp 函数时,系统将自动搜索可执行文件并执行它。新程序接收到的命令行参数将由 argv 提供。可以通过遍历 argv 数组来获取传递给新程序的参数
// 示例:char *argv[]={"ls", "-l", NULL}; execvp("ls", argv);
int execvp(const char *file, char *const argv[]);

// 示例:extern char **environ; char *const argv_[]={"ls", "-l", NULL}; execvpe("ls", argv_, environ);
// 规律:l(list)表示参数采用列表v(vector)参数用数组
; p(path)有p自动搜索环境变量PATH
; e(env)表示自己维护环境变量
int execvpe(const char *file, char *const argv[], char *const envp[]);

// 通过调用/bin/sh-c命令执行命令中指定的命令,并在命令完成后返回。在命令执行期间SIGCHLD将被阻塞,
并且SIGINT和SIGQUIT将被忽略
// 失败返回-1,成功就执行命令;如果命令为NULL,shell可用system()返回非零,如果不可用则返回零
// system()不会影响任何其他子项的等待状态,通过源码可以看出Linux系统下,system函数是execl函数的封装版
// 示例:system("top");
#include <stdlib.h>
int system(const char *command);

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

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

相关文章

vue3+ts mitt的使用

安装mitt :npm i mitt -Smain.ts: import mitt from mittconst Mit mitt();declare module vue {export interface ComponentCustomProperties{$Bus:typeof Mit} } app.config.globalProperties.$BusMit在A组件中使用 <template><div><h1>我是A<…

SSD FTL 映射管理

映射的种类 根据映射粒度的不同分为如下几种 1.块映射 一个逻辑块&#xff08;logical block&#xff09;映射到一个闪存物理块(physical block) 优点是&#xff1a;映射表需要空间小&#xff0c;对大Block size顺序读写&#xff0c;但是对小尺寸数据的写入性能很差。因为即使…

性能测试必看系列之Jmeter:硬件性能监控指标

硬件性能监控指标 一、性能监控初步介绍 性能测试的主要目标 1.在当前的服务器配置情况&#xff0c;最大的用户数 2.平均响应时间ART&#xff0c;找出时间较长的业务 3.每秒事务数TPS&#xff0c;服务器的处理能力 性能测试涉及的内容 1.客户端性能测试&#xff1a;web前…

【JavaSE】:数据类型

数据类型 一.总体概论二.java里与c的区别1.float2.char3.boolen 三.类型转换四.String类型 一.总体概论 在Java中数据类型主要分为两类&#xff1a;基本数据类型和引用数据类型。 不论是在16位系统还是32位系统&#xff0c;int都占用4个字节&#xff0c;long都占8个字节 。 整…

怎么给数据库某个字段建立一个前缀索引

说明&#xff1a;SQL调优中重要的一个环节是建立索引&#xff0c;其中有一条是字段值过长字段应该建立前缀索引&#xff0c;即根据字段值的前几位建立索引&#xff0c;像数据库中的密码字段、UUID字段。 因为其随机性&#xff0c;其实根据前几位就可以锁定某一条记录了。前缀索…

kafka开发环境搭建

文章目录 1 安装java环境1.1 下载linux下的安装包1.2 解压缩安装包1.3 解压后的文件移到/usr/lib目录下1.4 配置java环境变量 2 kafka的安装部署2.1 下载安装kafka2.2 配置和启动zookeeper2.3 启动和停止kafka 1 安装java环境 1.1 下载linux下的安装包 &#xff08;1&#xf…

2024年天津天狮学院专升本市场营销专业《市场营销学》考试大纲

2024年天津天狮学院专升本市场营销专业高职升本入学考试《市场营销学》考试大纲 一、考试性质 《市场营销学》专业课程考试是天津天狮学院市场营销专业高职升本入学考试的必 考科目之一&#xff0c;其性质是考核学生是否达到了升入本科继续学习的要求而进行的选拔性考试。《市…

RabbitMQ之延迟消息实战

RabbitMQ之延迟消息实战 使用死信交换机实现延迟消息 使用死信交换机的过期时间以及没有消费者进行消费&#xff0c;时间到了就会到死信队列中&#xff0c;由此可以实现延迟消息使用延迟消息插件 前提&#xff1a;需要mq配置插件 延时信息案例实战 把一个30分钟的延迟消息可以…

基于SpringBoot+Redis的前后端分离外卖项目-苍穹外卖(八)

套餐模块功能开发 1. 新增套餐1.1 需求分析和设计1.1.1产品原型&#xff1a;1.1.2接口设计&#xff1a;1.1.3数据库设计&#xff1a; 1.2 代码开发1.2.1 DishController层1.2.2 DishService接口类1.2.3 DishServiceImpl接口实现类1.2.4 DishMapper层1.2.5 DishMapper.xml1.2.6 …

C++ PCL点云dscan密度分割三维

程序示例精选 C PCL点云dscan密度分割三维 如需安装运行环境或远程调试&#xff0c;见文章底部个人QQ名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对《C PCL点云dscan密度分割三维》编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易读。…

npm中的npx命令

1.概念 npx是一个执行npm软件包的二进制文件&#xff0c;通俗的讲&#xff0c;他可以执行npm的一些指令。 2.示例 用babel将ES6语法转为ES5语法 npx babel src/js -d dist/js会执行babel的相关功能&#xff0c;如果没有安装&#xff0c;也会自动安装。 当在执行npx <co…

因式分解的几何意义

本来准备和女儿一起玩一道几何题&#xff0c;想想还是算了&#xff0c;不如讲点更有趣的。 任何因式分解都是在堆积木&#xff0c;不信你看&#xff1a; 二项式定理&#xff0c;洋灰三角&#xff0c;都是面积&#xff0c;体积&#xff0c;超维体积的拼接&#xff0c;一个大超…

【Linux】Linux项目自动化构建工具 --- make / makefile

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前正在学习c和Linux还有算法 ✈️专栏&#xff1a;Linux &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章有啥瑕疵&#xff0c;希望大佬指点一二 …

如何申请永久免费的SSL证书

首先&#xff0c;让我们了解什么是SSL证书。 SSL&#xff08;Secure Socket Layer&#xff09;证书是一种数字证书&#xff0c;它提供了一种在互联网上安全地传输数据的方法。 这是一个必须的安全工具&#xff0c;可以加密您的网站和客户之间的所有信息。为了保护用户数据和确保…

C语言实现万年历

C语言实现万年历 一、项目介绍 需求和功能是用纯C语言实现一个可以属于年份&#xff0c;属于一个年份就可以显示该年各个月份的日历&#xff0c;如同日历一般&#xff0c;每个月当中每天对应的星期均可查看&#xff0c;即万年历&#xff0c;要求格式整齐&#xff0c;星期对照直…

Rust语言入门教程(六) - 字符串类型

在Rust中&#xff0c; 字符串类型其实是一个比较复杂的话题。在Rust的标准库中&#xff0c;至少都提供了6种字符串类型&#xff0c;我们平常使用的最多的是其中的两种。这两种类型互相之间也有所关联&#xff1a; str&#xff1a; 字符串切片String 字符串 其中&#xff0c; 字…

【python】--文件/文件夹读写及操作

目录 一、文件读写1、文件读写代码示例 二、文件/文件夹操作1、代码示例 一、文件读写 读写文件就是请求操作系统打开一个文件对象&#xff08;通常称为文件描述符&#xff09;&#xff0c;然后通过操作系统提供的接口从这个文件对象中读取数据&#xff08;读文件&#xff09;…

混社会,要知道的“人性铁律”

混社会&#xff0c;要知道的“人性铁律”&#xff1a; 1.女人的私密生活&#xff0c;生理需求往往都是心口不一。 【闲聊】&#xff1a;一个人越是想要什么&#xff0c;往往嘴里越是说不在意&#xff0c;无论是男女。 2.普通人的思维&#xff0c;往往把宏大的社会变迁所造成…

Typescript基础面试题 | 04.精选 ts 面试题

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

论文阅读:“Model-based teeth reconstruction”

文章目录 AbstractIntroductionTeeth Prior ModelData PreparationParametric Teeth Model Teeth FittingTeeth Boundary Extraction Reference Abstract 近年来&#xff0c;基于图像的人脸重建方法日趋成熟。这些方法可以捕捉整个面部或面部特定区域&#xff08;如头发、眼睛…