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 COMMAND1、验证编译出来的a.out程序运行了两个进程ps -ajx | grep a.out | grep -v grep
2、查看a.out的进程号 pidps -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 grepkill 父进程PIDps -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.outpkill 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.hqwc.cn/news/229140.html

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

相关文章

基于多模态大数据的国家安全风险态势感知模型构建

源自&#xff1a;情报杂志 “人工智能技术与咨询” 发布 摘 要 [研究目的]为强化国家安全情报能力,推动风险监测预警能力提升,构建基于多模态大数据的国家安全风 险态势感知模型。 [研究方法]首先,对国家安全风险态势感知进行阐释;然后,分析多模态大数据驱动…

什么是算法?

一、是什么 算法&#xff08;Algorithm&#xff09;是指解题方案的准确而完整的描述&#xff0c;是一系列解决问题的清晰指令&#xff0c;算法代表着用系统的方法描述解决问题的策略机制 也就是说&#xff0c;能够对一定规范的输入&#xff0c;在有限时间内获得所要求的输出 …

交流负载的功能实现原理

交流负载的功能实现原理主要涉及到电力电子技术、电机控制技术和电力系统保护技术等多个方面。 交流负载的功能实现需要通过电力电子器件进行电能的转换和控制&#xff0c;电力电子器件主要包括开关器件和电力电子变压器等。开关器件主要用于实现电能的通断控制&#xff0c;如晶…

【古月居《ros入门21讲》学习笔记】12_服务端Server的编程实现

目录 说明&#xff1a; 1. 服务模型 说明 2. 实现过程&#xff08;C&#xff09; 创建服务器代码&#xff08;C&#xff09; 配置服务器代码编译规则 编译 运行 3. 实现过程&#xff08;Python&#xff09; 创建服务器代码&#xff08;Python&#xff09; 运行效果 说…

Buzz库python代码示例

Buzz库来编写一个下载器程序。 php <?php require_once vendor/autoload.php; // 引入Buzz库 use Buzz\Browser; use Buzz\Message\Response; $browser new Browser(); // 设置 $browser->setHttpClient(new HttpClientProxy([ host > , port > , ])…

list简单使用

目录 介绍 头文件 简单使用 Member functions Constructor operator ​编辑 Iterators Capacity empty size Element access: front/back Modifiers push_front pop_front push_back pop_back insert erase swap resize clear Operations remove uniq…

SSM训练营管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 训练营管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系 统主要采用B/S模…

电气制图用什么软件?CAD和Eplan哪个更胜一筹?

身为电气工程师&#xff0c;每天打交道最多的可能不是自家对象&#xff0c;而是时时刻刻攥在手里的电气图。目前市面上制作电路图的软件形形色色&#xff0c;但是AutoCAD Electrical和Eplan是目前大家使用率最高的两款电气制图软件。 EPLAN是一款专业的电气设计软件&#xff0…

Python字典类型

目录 目标 版本 官方文档 简介 实战 创建 循环 常用方法 目标 掌握字典类型的使用方法&#xff0c;包括&#xff1a;创建、循环、常用方法等操作。 版本 Python 3.12.0 官方文档 Mapping Types — dicthttps://docs.python.org/3/library/stdtypes.html#mapping-type…

发牌洗牌的简单逻辑

1. 需求分析 1.1 要求实现&#xff1a; 我们能使用一副牌&#xff0c;基本的实现多人炸金花小游戏。 1.2 实现分析&#xff1a; 1、有一副牌&#xff1a; 首先自定义card类&#xff0c;来定义每一张牌&#xff1b;&#xff08;牌上由花色和数字&#xff08;1~13&#xff09;&…

时尚和美容网站的技术 SEO:提示和最佳实践

如果你对美容和时尚感兴趣&#xff0c;做了一个网站&#xff0c;但不知道如何在上面做技术SEO&#xff1f;此外&#xff0c;时尚和美容网站的技术 SEO 没有任何特别的指南&#xff01; 我们听到了你的声音&#xff01;但首先&#xff0c;请记住&#xff0c;技术性SEO不是在一两…

解密Long型数据传递:Spring Boot后台如何避免精度丢失问题

前端和后端之间的数据传递至关重要。然而&#xff0c;当涉及到Long类型数据时&#xff0c;可能会出现精度丢失问题&#xff0c;这会影响数据的准确性。本文将为你介绍两种解决方案&#xff0c;帮助你确保Long类型数据在前端和后端之间的精确传递。 精度丢失测试 访问:http://l…