【嵌入式Linux】<总览> 多进程(更新中)

文章目录

前言

一、进程的概念与结构

1. 相关概念

2. 内核区中的进程结构

 3. 进程的状态

4. 获取进程ID函数

二、进程创建

1. fork和vfork函数

2. 额外注意点

3. 构建进程链

4.构建进程扇

 三、进程终止

1. C程序的启动过程

2. 进程终止方式

四、特殊的进程

1. 僵尸进程

2. 守护进程

3. 孤儿进程

五、相关函数

1. wait函数

2. waitpid函数

3. execl函数

4. execlp函数


前言

在Linux中程序的运行涉及进程的相关知识,熟悉并掌握其相关知识在嵌入式Linux应用开发中至关重要。本篇记录进程的具体知识,若涉及版权问题,请联系本人删除!


一、进程的概念与结构

1. 相关概念

  • 程序:存放在硬盘的可执行文件。
  • 进程:是程序运行的实例,每个进程都有一个虚拟地址空间。进程之间相互独立,同时也存在相关机制来进行进程的通信。每个Linux进程都有唯一的进程ID(PID),其都是正整数。
  • 并发:虚假的同时运行多个进程,是单CPU切换速度极快的结果。
  • 并行:真实的同时运行多个进程,有多个CPU。
  • 命令:①如下图,通过命令"ps -aux"可以查看进程信息。②用kill -9可以强制退出进程。

2. 内核区中的进程结构

每启动一个进程,在虚拟地址空间的内核区中就会对应一个task_struct结构体(进程控制块PCB),如下图所示。其中包含了进程的ID、状态、优先级、调度策略、文件结构体指针(指向文件描述符表)等等。

 3. 进程的状态

有五种常见状态:创建态、就绪态、运行态、阻塞态(挂起态)和退出态(终止态)。

  • 创建态:进程在创建时就是该状态,时间很短。
  • 就绪态:创建后就处于该状态,等待抢夺CPU时间片。
  • 运行态获得CPU资源使得该进程运行,当时间片用完后重新回到就绪态。
  • 阻塞态:进程强制放弃CPU,无法抢夺CPU时间片(例如sleep在休眠期间)。同时,阻塞态又分为不可中断和可中断类型。(执行中按下Ctrl+C能中断的是可中断类型)
  • 退出态:进程的终止,占用的系统资源被释放。(任何状态都可以直接转换为退出态)

僵尸状态:进程已经终止了,用户区资源已经被释放了,但是内核区中的task_struct仍有信息,ps的命令中STAT值为Z。

4. 获取进程ID函数

#include <unistd.h>
#include <sys/types.h>
当前进程ID: pid_t getpid(void);
当前进程的父进程ID: pid_t getppid(void);
当前进程的实际用户ID: uid_t getuid(void);
当前进程的有效用户ID: uid_t geteuid(void);
当前进程的用户组ID: gid_t getgid(void);
当前进程的进程组ID: pid_t getpgrp(void);
进程ID为pid的进程组ID: pid_t getpgid(pid_t pid);【注】实际用户是当前环境下的用户,有效用户是真正开启进程的用户


二、进程创建

1. fork和vfork函数

【1】头文件:#include <sys/types.h>、#include <unistd.h>

【2】函数原型:①pid_t fork(void); ②pid_t vfork(void);

【3】功能:

  • fork创建子进程,且子进程复制父进程的内存空间。子、父进程谁先运行看进程调度。
  • vfork创建子进程,子进程先运行不复制父进程空间。

2. 额外注意点

  • fork和vfork被调用一次,会返回两次:子进程中的返回值为0,在父进程中的返回值则是子进程的PID。可以根据返回值不同来区分是父进程还是子进程。
  • 失败返回值:创建子进程失败会返回-1。
  • 执行位置:父进程是从main函数代码体首部开始执行,子进程是从fork函数之后开始执行。
  • 虚拟地址空间的用户空间:子进程中代码段与环境变量的物理空间和父进程是同一个。而其他的物理空间不是同一个(而是将父进程的复制一份给子进程),即使它们的虚拟地址是一样的。
  • 虚拟地址空间的内核空间:①子进程只复制父进程的文件描述符表,不复制但共享文件表项和inode。②父进程创建一个子进程后,文件表项中的引用计数器加1,当父进程close后计数器减1,子进程还是可以使用文件表项,只有当计数器为0时才会释放文件表项。

实验程序1:创建子进程,打印子、父进程中的pid信息。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{//fork创建子进程,复制父进程空间pid_t pid = fork();//子、父进程中打印pidif (pid < 0) {perror("创建子进程失败");} else if (pid == 0) {//子进程printf("I am child process. PID: %d, PPID: %d, 返回的PID: %d\n", getpid(), getppid(), pid);} else {//父进程printf("I am parent process. PID: %d, PPID: %d, 返回的PID: %d\n", getpid(), getppid(), pid);}return 0;
}

实验程序2:父进程将文件指针定位到文件尾部,子进程写入内容。原有目录下有文件1.txt,原有内容为123

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main(int argc, char **argv)
{//命令行参数判定if (argc != 2) {printf("Command: %s <filename>\n", argv[0]);return -1;}//文件操作int fd = open(argv[1], O_WRONLY);if (fd < 0) {perror("文件打开错误");return -1;}//父进程改变文件指针到文件尾部//子进程等待父进程定位好后写入内容pid_t pid = fork();if (pid < 0) {perror("创建子进程错误");close(fd);return -1;} else if (pid > 0) {//父进程if (lseek(fd, 0, SEEK_END) < 0) {perror("文件指针定位错误");close(fd);return -1;}} else {//子进程sleep(2);//确保父进程先运行const char * content = "Hello, Can!\n";int contentSize = strlen(content);if (write(fd, content, contentSize) < contentSize) {printf("写入错误\n");close(fd);return -1;}}printf("--------pid: %d完成工作---------\n", getpid());//关闭文件:父子进程都会关闭,使得引用计数减为0close(fd);return 0;
}

3. 构建进程链

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{//创建3个子进程,形成进程链for (int i = 0; i < 3; ++i) {pid_t pid = fork();if (pid < 0) {perror("创建失败");return -1;}if (pid > 0) { //若为父进程则退出break;}}printf("PID: %d, PPID: %d\n", getpid(), getppid());sleep(1);return 0;
}

4.构建进程扇

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{//创建3个子进程,形成进程扇for (int i = 0; i < 3; ++i) {pid_t pid = fork();if (pid < 0) {perror("创建失败");return -1;}if (pid == 0) {//若为子进程则退出break;}}printf("PID: %d, PPID: %d\n", getpid(), getppid());sleep(1);return 0;
}


 三、进程终止

1. C程序的启动过程

在main函数执行前,Linux内核会启动一个特殊例程,将命令行中的参数传给argc和argv。若主函数中有三个形参,那么该例程还会将环境信息构建成环境表传给第三个形参。最后,该例程还会登记进程的终止函数(进程终止前会调用)。

终止函数说明:

  • 每个进程都默认登记了一个标准的终止函数。
  • 终止函数在进程终止时释放一些资源。
  • 登记的多个终止函数的执行顺序按照的方式执行。
  • 用户自定义终止函数(无参无返回值),需要调用atexit函数向内核登记。

atexit函数:

【1】头文件:#include <stdlib.h>

【2】功能:向内核登记一个终止函数,该函数会在正常进程终止时被调用。

【3】函数原型:int atexit(void (*function)(void));

【4】返回值:成功返回0,否则返回非零值。

2. 进程终止方式

  • 正常终止:
    • ①main函数中return返回 会刷新标准IO缓存,会执行自定义的终止函数
    • ②调用库函数exit(0) 会刷新标准IO缓存,会执行自定义的终止函数
    • ③调用系统调用函数_exit(0)或_Exit(0) 不会刷新标准IO缓存,不会执行自定义的终止函数
    • ④最后一个线程从其启动例程返回
    • ⑤最后一个线程调用库函数pthread_exit
  • 异常终止:
    • ①调用库函数abort
    • ②接收到信号并终止(例如段错误会产生一个信号,然后终止进程)
    • ③最后一个线程对取消请求做处理响应

实验程序:运行下列代码,若参数指定为exit或return,文件中有写入的字符串,并且会执行自定义的终止函数;若参数指定为_exit,文件中没有任何内容,并且没有执行终止函数。

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>//自定义终止函数
void fun1() {printf("Terminate: fun1\n");
}void fun2() {printf("Terminate: fun2\n");
}void fun3() {printf("Terminate: fun3\n");
}//主函数
int main(int argc, char **argv)
{//命令行参数判定if (argc != 3) {printf("commnd: %s <filename> <exit | return | _exit>\n", argv[0]);return -1;}//登记自定义终止函数atexit(fun1);atexit(fun2);atexit(fun3);//文件操作,忽视健壮性判定FILE *fd = fopen(argv[1], "w");//文件不存在则创建,调用失败返回NULLfprintf(fd, "Hello, world!\n");//向文件缓冲区写入字符串,若没有刷新或fclose则不会写入硬盘//根据参数选择退出方式if (!strcmp(argv[2], "exit")) {exit(0);} else if (!strcmp(argv[2], "return")) {return 0;} else {_exit(0);}
}


四、特殊的进程

1. 僵尸进程

  • 概念:子进程的虚拟地址空间中的用户区资源已经释放,但内核区中的task_struct没有被释放,那么该进程就是僵尸进程。
  • 释放僵尸进程的方式:
    • ①结束或kill僵尸进程的父进程,那么僵尸进程就会成为孤儿进程,然后会被init进程(1号进程)领养,最终会被回收。
    • ②让僵尸进程的父进程来回收。父进程每隔一段时间就查询子进程是否结束并回收,调用wait函数或waitpid函数,通过内核来释放僵尸进程。
    • ③采用信号SIGCHLD通知处理,在信号处理函数中调用wait函数。

程序示例:运行如下程序,就会生成僵尸进程。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{//创建子进程pid_t pid = fork();if (pid < 0) {perror("创建子进程失败");return -1;}//子进程退出,成为僵尸进程if (pid == 0) {printf("PID: %d, PPID: %d\n", getpid(), getppid());return -1;}//父进程循环,便于观察while(1) {sleep(1);}return 0;
}

2. 守护进程

  • 概念:是一种生存期很长的进程。从操作系统启动开始,在操作系统关闭时终止。
  • 所有守护进程都以root(用户ID为0)的优先权运行。
  • 守护进程没有控制终端,一直在后台运行。
  • 守护进程的父进程都是init进程。

3. 孤儿进程

  • 概念:父进程结束了,但是子进程还在运行,那么此时子进程就是孤儿进程。孤儿进程由init进程(1号进程)来回收。
  • 领养机制引入:进程的用户区资源可以自己释放,但是内核区资源需要由父进程释放。而孤儿进程的父进程已经结束。因此,为了释放孤儿进程的内核区资源,让1号进程来领养它,进而释放其内核区的task_struct结构体。

程序示例:通过fork创建子进程,同时让父进程退出,那么子进程就是孤儿进程。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char **argv)
{//创建子进程pid_t pid = fork();if (pid < 0) {perror("创建子进程失败");return -1;}//父进程退出if (pid > 0) {printf("PID: %d, PPID: %d\n", getpid(), getppid());return -1;}//子进程成为孤儿进程if (pid == 0) {sleep(2);printf("PID: %d, PPID: %d\n", getpid(), getppid());return -1;}return 0;
}


五、相关函数

1. wait函数

【1】头文件:#include <sys/types.h>、#include <sys/wait.h>

【2】函数原型:pid_t wait(int *wstatus);

【3】参数说明:wstatus是传出的参数,存放子进程退出时的信息。例如:wait(7status);

取出整形变量status中的数据需要使用一些宏函数:

  • WIFEXITED(status)用于判定是否是正常结束,是的话返回真;WEXITSTATUS(status)取出对应的进程退出码。
  • WIFSIGNALED(status)用于判定是否是异常结束,是的话返回真;WTERMSIG(status)取出对应的进程退出码。
  • WIFSTOPPED(status)用于判定是否是暂停子进程的返回,是的话返回真;WSTOPSIG(status)取出对应的进程退出码。

【4】功能:父进程等待子进程退出并回收,避免僵尸进程和孤儿进程产生。

【5】返回值:成功则返回子进程的PID,失败返回-1

【6】注意:wait函数等待所有的子进程退出。

示例程序:演示子进程异常退出,父进程对退出码进行处理。

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>int main(int argc, char **argv)
{//创建子进程pid_t pid = fork();if (pid < 0) {perror("创建子进程失败");return -1;}//子进程:打印信息,异常退出if (pid == 0) {printf("PID: %d, PPID: %d\n", getpid(), getppid());int i = 3, j = 0, k = i/j;//由于除0异常退出}//父进程:阻塞等待子进程退出,将退出码保存int status;pid_t ret = wait(&status);if (ret < 0) {printf("回收失败\n");} else {printf("回收成功,子进程PID:%d\n", ret);}//父进程:处理退出码if(WIFEXITED(status)) {printf("正常退出:%d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("异常退出:%d\n", WTERMSIG(status));} else if (WIFSTOPPED(status)) {printf("暂停退出:%d\n", WSTOPSIG(status));} else {printf("未知退出\n");}return 0;
}

2. waitpid函数

3. execl函数

4. execlp函数

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

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

相关文章

免费体验软件开发生产线 CodeArts

软件开发生产线 CodeArts 一站式、全流程、安全可信的软件开发生产线&#xff0c;开箱即用&#xff0c;内置华为多年研发最佳实践&#xff0c;助力效能倍增和数字化转型 免费试用体验版套餐&#xff0c;50人内免费试用 功能特性 Scrum和看板需求模型 代码托管 代码检查&am…

GIS开发如何高质量就业?这几点是关键!

高质量就业&#xff0c;包含薪资和其他福利待遇&#xff0c;在讨论如何高质量就业之前&#xff0c;我们先来看下GIS开发岗位的前景、薪资水平如何&#xff1f;最后讨论一下GIS开发工程师到底需要学习哪些技术&#xff1f; 01 GIS开发岗位呈持续上升趋势 从GIS开发岗位趋势也可…

Java知识点整理 11— 后端 Spring Boot 万用初始化模板使用

一. 模块简介 annotation&#xff1a;自定义注解aop&#xff1a;请求日志和权限校验common&#xff1a;通用类config&#xff1a;配置类constant&#xff1a;常量 controller&#xff1a;控制层esdao&#xff1a;方便操作ESexception&#xff1a;异常类job&#xff1a;定时任务…

Facebook广告投放的6个误区,老手也会犯

一、没有目标 无论是投放哪种产品&#xff0c;我们始终都需要明确&#xff0c;广告的目标是什么。 因为Facebook广告的形式和类型&#xff0c;也经常会有变化&#xff0c;例如近期Facebook推出的360视频广告&#xff0c;以及之后即将推出的LIVE&#xff0c;Mid-Roll视频插播广…

美国电商选品、大促、趋势、案例,掌慧科技首期NewsBreak沙龙干货满满

今年第一季度&#xff0c;美国电商销售额达到了2681.2亿美元&#xff0c;相较上一年同期的2471.8亿美元增长8.5%。同时&#xff0c;该季度美国电商销售额在零售业总销售额中的占比为22.2%&#xff0c;高于上一年同期的21.2%。美国在2023年下半年通胀得到良好控制&#xff0c;20…

CleanMyMac2024破解版下载链接!你的Mac清洁利器!

嘿&#xff0c;亲爱的朋友们&#xff0c;今天我要跟大家分享一款我最近超级依赖的电脑清理神器—CleanMyMac2024破解版&#xff01;如果你还在为电脑运行缓慢、存储空间不够而烦恼&#xff0c;那你一定不能错过它&#xff01; &#x1f525; 为什么选择CleanMyMac2024破解版&am…

声波的种类

声波可以根据不同的特性进行分类&#xff0c;主要包括频率和传播方式两个方面&#xff1a; ### 按频率分类&#xff1a; 1. **次声波**&#xff1a;频率低于20Hz的机械波&#xff0c;这类波通常不能被人耳感知。 2. **可闻声波**&#xff1a;频率在20Hz至20kHz之间的机械波&am…

C++ | Leetcode C++题解之第160题相交链表

题目&#xff1a; 题解&#xff1a; class Solution { public:ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {if (headA nullptr || headB nullptr) {return nullptr;}ListNode *pA headA, *pB headB;while (pA ! pB) {pA pA nullptr ? headB : p…

【fiddler】fiddler抓取websocket

1.先了解websocket流 下载4.5版本以上的fiddler 如图所示&#xff1a;在rules--customize rules 里面插入以下代码&#xff1a; static function OnWebSocketMessage(oMsg: WebSocketMessage) { // Log Message to the LOG tab FiddlerApplication.Log.LogString(oMsg.ToStr…

鸿蒙开发下拉选项框在表单递交的处理

下拉选项框 <select name"identity"><option value"0">顾 客</option><option value"1">行 政</option><option value"2" >保 洁</option></select>在表单数据中没有找到identit…

Win11 Docker Desktop下部署springboot jar

1.将springboot程序使用maven package打包出jar。 2.创建dockerfile&#xff0c;为了本地打包时方便&#xff0c;这里的dockerfile有小变动。 # Docker Desktop下部署springboot jar FROM openjdk:8 VOLUME /tmp EXPOSE 8601 ARG JAR_FILEtarget/webflux-hello-0.0.1-SNAPSHO…

AVL树插入详解

1.什么是AVL树 二叉搜索树可以提高搜索的效率&#xff0c;但是如果数据有序或者接近有序&#xff0c;就会退化为单边树&#xff0c;查找效率相当于在顺序表中查找数据&#xff0c;时间复杂度会退化到O(n)。AVL树解决了这个问题&#xff0c;通过保证每个节点的左右子树高度之差…

重学java 83.Java注解

As a failure,I met my last sound. —— 24.6.24 一、注解的介绍 1.引用数据类型: 类、数组、接口、枚举、注解 jdk1.5版本的新特性 一个引用数据类型 和类,接口,枚举是同一个层次的 引用数据类型:类、数组、接口、枚举、注解 2.作用: ① 说明&#xff1a;对代码进行说明,生…

elementui组件库实现电影选座面板demo

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Cinema Seat Selection</title><!-- 引入E…

前端中的深拷贝

第1部分&#xff1a;引言 深拷贝&#xff1a;前端开发的隐形守护者 在前端开发的世界里&#xff0c;数据的传递和状态的管理是构建用户界面的基础。然而&#xff0c;数据的复制常常被忽视&#xff0c;直到它引发bug&#xff0c;我们才意识到它的重要性。深拷贝&#xff0c;这…

122.网络游戏逆向分析与漏洞攻防-邮件系统数据分析-邮件物品箱的管理

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

【论文精读】ViM: Out-Of-Distribution with Virtual-logit Matching 使用虚拟分对数匹配的分布外检测

文章目录 一、文章概览&#xff08;一&#xff09;问题来源&#xff08;二&#xff09;文章的主要工作&#xff08;三&#xff09;相关研究 二、动机&#xff1a;Logits 中缺失的信息&#xff08;一&#xff09;logits&#xff08;三&#xff09;基于零空间的 OOD 评分&#xf…

你还在手动操作仓库?这款 CLI 工具让你效率飙升300%!

前言 作为一名开发者&#xff0c;我经常会在 GitHub 和 Gitee 上 fork 各种项目。时间一长&#xff0c;这些仓库就会堆积如山&#xff0c;变成了“垃圾仓库”。每次打开代码托管平台&#xff0c;看到那些不再需要的仓库&#xff0c;我的强迫症就会发作。手动一个一个删除这些仓…

统计学三学习笔记

一&#xff0c;t分布 二&#xff0c;置信区间 最终要用② n越大&#xff0c;s越小&#xff0c;置信区间越小 三&#xff0c;配对样本t检验 假如有两个族群&#xff1a;

【Godot4自学手册】第四十二节实现拖拽进行物品交换和数量叠加

这一节我们主要学习背包系统中的物品拖拽后&#xff0c;物品放到新的位置&#xff0c;或交换物品位置&#xff0c;如果两个物品属于同一物品则数量相加。具体效果如下&#xff1a; 一、修改item.tscn场景 给item.tscn场景的根节点Item添加Label子节点&#xff0c;命名为Numv…