Linux C语言 22-多进程

news2024/11/30 12:31:57

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);

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

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

相关文章

WordPress安装AWS插件实现文本转语音功能

适用于 WordPress 的 AWS 插件示例演示了内容创建者如何轻松地为所有书面内容添加文本转语音功能。随着语音搜索的不断增加&#xff0c;以音频格式提供更多网站内容变得至关重要。通过添加语音功能&#xff0c;网站访客可以通过在线音频播放器和播客应用程序等新渠道使用您的内…

【代码】平抑风电波动的电-氢混合储能容量优化配置(完美复现)matlab-yalmip-cplex/gurobi

程序名称&#xff1a;平抑风电波动的电-氢混合储能容量优化配置 实现平台&#xff1a;matlab-yalmip-cplex/gurobi 代码简介&#xff1a;针对电-氢混合系统协同平抑接入新型电力系统的 新能源波动问题&#xff0c;提出考虑碱性电解槽运行特性的电-氢 混合储能容量优化配置方案…

鸿蒙开发报错:agconnect sdk not initialized. please call initialize()【BUG已解决】

文章目录 项目场景:问题描述原因分析:解决方案:总结:项目场景: 鸿蒙开发报错: agconnect sdk not initialized. please call initialize() 问题描述 报错内容为: 10-25 11:41:01.152 6076-16676 E A0c0d0/JSApp: app Log: 数据查询失败: {“code”:1100001,“messag…

Ps:拾色器 - 选取专色

在 Adobe 拾色器中&#xff0c;可点击“颜色库” Color Libraries按钮来选取专色。 首先在色库 Book列表中选择对应的色库&#xff0c;然后在中间的色相条中选择需要的样本组&#xff0c;再从左侧颜色列表中选取颜色。 可以直接键入颜色名称来选择。比如&#xff0c;键入 13&am…

解决视口动画插件jquery.aniview.js使用animate.css时无效的问题(最新版本网页视口动画插件的使用及没作用、没反应)

当网站页面元素进入视口时自动应用过渡效果。CSS过渡效果可以为网页添加动画效果&#xff0c;并提供了一种平滑的转换方式&#xff0c;使元素的变化更加流畅和生动。而通过jQuery插件来获取页面滚动位置决定合适调用动画效果。 一、官网 animate.css官网 一款强大的预设css3动…

MYSQL基础之【创建数据表,删除数据表】

文章目录 前言MySQL 创建数据表通过命令提示符创建表使用PHP脚本创建数据表 MySQL 删除数据表在命令提示窗口中删除数据表使用PHP脚本删除数据表 后言 前言 hello world欢迎来到前端的新世界 &#x1f61c;当前文章系列专栏&#xff1a;Mysql &#x1f431;‍&#x1f453;博主…

【数据结构实验】树(一)构建二叉查找树(BST)

文章目录 1. 引言2. 二叉查找树3. 实验内容3.1 实验题目&#xff08;一&#xff09;输入要求&#xff08;二&#xff09;输出要求 3.2 算法实现1. 数据结构2. 全局变量3. 中序遍历函数InOrder4. 二叉查找树的构建函数T5. 主函数 3.3 代码整合 4. 实验结果 1. 引言 二叉查找树&a…

公交路线查询系统

公交路线查询系统 一&#xff1a;目标一&#xff1a;类的定义构造方法 set和get方法&#xff1a;目标二&#xff1a;静态属性 静态方法 toString方法&#xff1a;目标三&#xff1a;抽象类的定义 抽象方法 实际应用&#xff1a;abstract class AbstractRoute{目标四&#xff1…

在项目中集成marsUI

拷贝文件夹到目标项目 集成 安装相关依赖 npm i --save ant-design-vue4.x npm i less npm i nprogress npm i consola npm i echarts npm i vue-color-kit npm i icon-park/svg npm i vite-plugin-style-import 配置Vite文件 使用 效果

MySQL的基础知识

目录 关系型数据库 SQL通用语法 数据类型 数值类型 字符串类型 日期类型 SQL分类 DDL 数据库操作 表操作 DML 添加数据 修改数据 删除数据 DQL 基本查询 条件查询 聚合函数 分组查询 排序查询 分页查询 执行顺序 DCL 管理用户 权限控制 函数 字符串…

基于YOLOv5的视频计数 — 汽车计数实现

在视频中计数对象可能看起来有挑战性&#xff0c;但借助Python和OpenCV的强大功能&#xff0c;变得令人意外地易于实现。在本文中&#xff0c;我们将探讨如何使用YOLO&#xff08;You Only Look Once&#xff09;目标检测模型在视频流或文件中计数对象。我们将该过程分解为简单…

手摸手Element-ui组件化开发

前端环境准备 编码工具: VSCode 依赖管理:NPM 项目构建: Vuecli NPM的全称是Node Package Manager&#xff0c;是一个NodeJS包管理和分发工具&#xff0c;已经成为了非官方的发布Node模块&#xff08;包&#xff09;的标准。2020年3月17日&#xff0c;Github宣布收购npm&am…

Node.js入门指南(四)

目录 express框架 express介绍 express使用 express路由 express 响应设置 中间件 路由模块化 EJS 模板引擎 express-generator hello&#xff0c;大家好&#xff01;上一篇文章我们介绍了Node.js的模块化以及包管理工具等知识&#xff0c;这篇文章主要给大家分享Nod…

【代码】基于VMD(变分模态分解)-SSA(麻雀搜索算法优化)-LSTM的光伏功率预测模型(完美复现)matlab代码

程序名称&#xff1a;基于VMD&#xff08;变分模态分解&#xff09;-SSA&#xff08;麻雀搜索算法优化&#xff09;-LSTM的光伏功率预测模型 实现平台&#xff1a;matlab 代码简介&#xff1a;提出了变分模态分解(VMD)和麻雀搜索算法(SSA)与长短期记忆神经网络 (LSTM)相耦合,…

佳易王各行业收银管理系统软件,企业ERP管理软件,企业或个体定制开发软件以及软件教程资源下载总目录,持续更新,可关注收藏查阅

系统简介 1、佳易王软件功能实用、操作简单、软件绿色免安装&#xff0c;解压即可使用&#xff0c;软件已经内置数据库&#xff0c;不需再安装其他数据库文件。 2、佳易王软件&#xff0c;已经形成系列&#xff0c;上百款管理系统软件涵盖多个行业。 3、已为多个企业个体定制…

CSS特效019:图标图片悬浮旋转一周

CSS常用示例100专栏目录 本专栏记录的是经常使用的CSS示例与技巧&#xff0c;主要包含CSS布局&#xff0c;CSS特效&#xff0c;CSS花边信息三部分内容。其中CSS布局主要是列出一些常用的CSS布局信息点&#xff0c;CSS特效主要是一些动画示例&#xff0c;CSS花边是描述了一些CSS…

Android WiFi的断开分析

1.wifi断开大体流程&#xff1a; 1.wifi断开 wlan-driver最先知道。 2.wlan-driver在与路由器连接的时候(未断开时), 会有周期性的beacon帧来维持连接&#xff0c;AP端一旦遇到突发事情&#xff0c;会立刻通过802.11协议的 deauth 帧/ reject 帧等 通知到 driver。 3. wlan-…

计算器的模拟实现

计算器的模拟实现 一、实验题目&#xff1a;计算器二&#xff1a;实验目的&#xff1a;三&#xff1a;实验内容与实现1&#xff1a;【实验内容】2&#xff1a;【实验实现】1.计算器界面的实现&#xff0c;如下图所示&#xff1a;2&#xff1a;各项功能的实现&#xff0c;如下图…

canvas版放大镜

效果预览图 完整代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>放大镜</title><st…

C++ vector迭代器失效

STL中vector迭代器失效常见错误写法示例 最近在看STL容器失效的例子&#xff0c;涉及到vector数组迭代器失效的问题&#xff0c;如果不注意使用&#xff0c;很容易出现问题&#xff0c;我们先来看一下一个简单的示例程序&#xff0c;在数组nums中删除大于50的元素&#xff0c;…