当前位置: 首页 > news >正文

Linux应用(3)——进程控制

一、简介

1.1 什么是进程

单进程:一个正在进行/运行的程序
多进程:多个正在进行/运行的程序

1.2 进程特点

能运行: 肯定存在main主函数
没结束: 程序运行没结束
内存空间:程序运行后,才变为进程,系统才会给进程分配一块运行空间

1.3 进程的用途

解决多功能的并发运行问题,当单核CPU需要并发执行多个功能时,采用裸机处理思想(标志位/中断)复杂度较高;为了解决该问题,引入了操作系统
M系列MCU中:引入小型的实时操作系统:ucos freertos liteos  系统中都是 多任务 概念
A系列MCU中:引入大型的分时操作系统:linux 鸿蒙 安卓   系统中都是多进程/多线程
多进程控制、通信 实际上就是应用算法: 主要用于系统的应用开发中

1.4 进程状态

简单认为进程状态: 就绪态、运行态、阻塞态、死亡态

R (running)   :运行状态 表明进程要么是在运行中要么在运队列。
S (sleeping)  :浅度睡眠状态
D (disk sleep) :不可中断睡眠状态 此状态只能自己醒过来,在这个状态的进程通常会等待IO的结束。
T (stopped)  :停止状态
X (dead)       :死亡状态
Z (zombie)   :僵尸状态 此进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。

1.5 进程与freertos任务对比

对比

linux

freertos

用途上

解决CPU多功能并发运行问题

解决CPU多功能并发运行问题

功能名称上

多进程、多线程

多任务

功能状态上

运行态、阻塞态、死亡态

运行态、阻塞态、死亡态

功能切换上

  1. 阻塞类相关API函数
  2. 公平/权重为主、优先级抢占为辅
  1. 阻塞类相关API函数
  2. 优先级抢占为主、同优先级时间片为辅

1.6 进程相关系统命令

命令1: ps  -ef     用来查看进程的PID 和本身父进程的PPID

命令2:  ps  -aux   用来查看进程的CPU占用率、MEM占用率、 STAT状态

命令3: kill   -信号值   进程PID    用来给指定进程发送信号
举例:  kill  -9  7689

1.7 进程相关名词

祖先进程:PID固定是1,是linux系统启动后运行的第一个进程
守候进程:孤儿进程就是守候进程的一种;脱离了终端的进程--后台进程
父子进程:是linux下进程的基本框架,任何一个进程都存在父进程(不是随便产生的)
孤儿进程:
父进程先于子进程结束,这时子进程就变成孤儿进程,这时子进程就会被祖先进程接收((也就类似于祖先进程是子进程的父进程了),
影响:孤儿进程并不会对系统造成危害,因为init进程会负责清理它们。实际上,这是Unix/Linux系统的一种机制,确保进程不会因为父进程的退出而失去管理。
僵尸进程:
子进程先于父进程结束,父进程没有回收子进程占用的内存空间,这时子进程就变成僵尸进程
 影响:僵尸进程不占用系统资源(如内存),但是会占用进程表中的一个位置。如果大量僵尸进程积累,可能会导致系统无法创建新的进程(因为进程表已满)

总结:
孤儿进程:父进程先退出,子进程被init接管,不会造成危害。
僵尸进程:子进程退出,父进程未读取其退出状态,会占用进程表条目,可能造成资源浪费(进程表资源)。
所以从上可知:
1.孤儿进程、僵尸进程 都是针对于 子进程是孤儿还是僵尸
2.孤儿进程、僵尸进程都是不好的状态,都是代码的bug ,都需要优化代码
进程中代码优化(核心: 避免出现孤儿进程 或者僵尸进程)
---理想的结束状态:子进程先于父进程结束,父进程先回收子进程内存空间,然后在结束父进程

二、进程的控制方式

进程的控制方式存在两种:
方式1:在一个进程中运行另一个进程
方式2:在一个进程中创建另一个进程--父子进程

2.1 在一个进程中运行另一个进程

特点

  1. 另一个进程必须是存在且能用运行的
  2. 实现方式:采用 system 或 exec 函数 
  3. 用途: 多功能菜单操作

函数

system 函数应用

头文件:#include <stdlib.h>
函数原型:int system(const char *command);
函数参数:const char *command : 要运行进程的可执行命令(系统命令、用户自定义命令)
函数返回值:-1:  执行失败
函数功能:执行一个可执行命令 (进程运行)
函数特性:当可执行命令中存在参数并参数通过外部得到时,可以借助于strcat或sprintf 完成完整地可执行命令,然后再作为实参传入

举例:system("cd/tmp;mkdir test_dir;echo '创建成功'");
或system("cd/tmp && mkdir test_dir &&echo '创建成功'");

exec 函数簇

是由多个以exec作为函数名开端的同功能函数,是 Linux/Unix 系统编程中一组重要的函数,用于执行新的程序,替换当前进程的映像。这些函数在进程控制中起着核心作用。

头文件:#include <unistd.h>
函数原型:
int execl(const char *path, const char *arg, ... /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
函数参数:
@param: const char *path  : 包含可执行命令所在的绝对路径   举例   "/bin/ls"
@param: const char *file    :  可执行命令名                               举例  "ls"
 @param: const char *arg, ... /* (char  *) NULL */  : 包含可执行命令名的所有参数,必须以 NULL 结尾                                                                                       举例  "ls","-l",NULL
@param:  char *const argv[] : 把包含可执行命令名的所有参数封装到数组中,必须以NULL结尾,把数组作为实参                                   举例:char *const argv[] ={ "ls","-l",NULL};
@param:  char * const envp[]  :  设置的环境变量,必须以 NULL 结尾 "PATH=你所使用可执行命令所在的绝对路径", NULL                 举例   "PATH=/bin", NULL
函数返回值: 调用是否成功 
这些函数的区别在于:
1.参数传递方式:参数列表(l)还是数组(v)
2.是否使用环境变量PATH查找可执行文件(p)
如果 没有p :  可执行命令就需要绝对路径形式表示
如果  有 p :   可执行命令无需绝对路径
3.是否传递自定义环境变量(e)
如果 没有 e ;  不需要环境变量作为参数
如果 有    e  :  需要环境变量作为参数
----从上述来看   使用比较简单的函数  execlp  或者   execvp

以  "touch  1.txt  2.txt"  举例  

函数  execl应用: execl("/bin/touch", "touch","1.txt","2.txt",NULL);
函数 execlp 应用:execlp("touch","touch","1.txt","2.txt",NULL );
函数execle应用:
char * const envp[]={"PATH=/bin",NULL};
execle("/bin/touch", "touch","1.txt","2.txt",NULL,  envp);
函数execv应用:
char *const argv[]={"touch","1.txt","2.txt",NULL};
execv("/bin/touch", argv);
函数execvp应用:
char *const argv[]={"touch","1.txt","2.txt",NULL};
execvp("touch", argv);
函数execvpe应用
char *const argv[]={"touch","1.txt","2.txt",NULL};// 参数数组
char * const envp[]={"PATH=/bin",NULL};//环境变量数组
execvpe("touch",  argv,  envp);

system与exec对比

system会阻塞当前进程直到命令执行完成,然后继续执行后续代码。
exec成功时不会返回,当前进程被新程序完全替换,执行完后原进程不再执行--通常会用在父子进程中,结束父子进程的运行

2.2 在一个进程中创建另一个进程

特点

  1. 另一个进程是不存在,所有要创建
  2. 新创建的进程---就是子进程;原来的进程--就是父进程
  3. 父子进程是多进程的一种特例
    多进程: 多个独立的带有main主函数的程序
    父子进程:还是只有一个main主函数,分成了父子进程
  4. 用途
    父子进程: 主要用于处理2个关系比较密切的并发功能
    多进程  : 主要用于处理多个关系不密切的并发功能
    多线程  : 主要用于处理多个关系比较密切的并发功能

函数

fork    vfork  进程创建
getpid   getppid  得到自身进程PID ,和得到自身父进程的ppid
exit  _exit    return  进程的退出
wait  waitpid  进程的等待(进程空间的回收)

fork  vfork函数 

头文件:#include <unistd.h>
函数原型:pid_t fork(void);
函数参数:
函数返回值:pid_t
<0: 创建失败;
==0: 返回子进程操作
>0  :  返回父进程操作 (具体数值就是子进程的PID)
函数功能:
它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程函数执行顺序:
1.该函数调用一次,会依次产生两个返回值(==0  和 >0)
2.先返回哪个数值是不确定的,通常先返回 >0的
3.当返回>0数值时就表示进入父进程操作,在父进程中遇到阻塞类相关API函数,父进程就会进入阻塞态
4.父进程进入阻塞态,这时就再次返回 ==0数值,就表示进入子进程操作在子进程中遇到阻塞类相关API函数,子进程就会进入阻塞态
5.这时CPU就会在父子进程之间切换 (运行、就绪、阻塞、死亡)
6. fork前内容是父进程独有,只会执行一次;fork后内容父子进程共有,共有的内容会执行两次(父进程执行一次,子进程执行一次),所有为了操作的方便,fork后最好不要设计父子共有的内容,全部设计成父子独有内容(独有内容只会执行一次)

函数特性:
1.该函数调用一次,会依次产生两个返回值(==0  和 >0)
2.写时复制:创建的子进程复制父进程的内存空间,父子进程占用不同的内存空间,父子进程内数据之间不会相互干扰
3.所以为了操作方便,在父子进程独有操作中单独定义数据,不要在fork前定义数据
4.资源继承: 子进程继承父进程的文件描述符也就是说:在fork前open的文件,是父子进程共有的

函数框架

函数框架
pid_t  pid;//进程PID
pid =fork();// 进程创建
/* 在if之前添加的内容 就都是共有的*/
if(pid <0)// 基本不会执行到
{perror("fork error\n");return -1;
}
else if(pid==0)// 返回子进程操作
{/* 子进程独有*/
// 在这里定义子进程所需数据while(1){ }
}
else if(pid>0)// 或者 else 返回父进程操作
{/* 父进程独有*/// 在这里定义父进程所需数据while(1){  }
}
/* 在if形式后添加的内容 就都是共有的*/

getpid getppid 函数

头文件:#include<unistd.h>
函数原型:pid_t getpid(void);
函数返回值: 得到自身的PID
该函数在哪个进程中调用,就得到哪个进程自身的PID

头文件:#include<unistd.h>
函数原型:pid_t getppid(void);
函数返回值: 得到自身父进程的PPID
该函数在哪个进程中调用,就得到哪个进程父进程的PPID

exit 、_exit、return 函数区别

进程的退出--结束
#include <stdlib.h>
void exit(int status);
函数参数: 会先刷新一下缓冲区,然后结束进程;

结束进程时的返回状态  ,类似于 return 数值;

进程的退出--结束
#include <unistd.h>
void _exit(int status);
函数参数:不会刷新缓冲区,直接结束进程;
结束进程时的返回状态  ,类似于 return 数值;

举例1:
printf("hello\n");
exit(0);// 等价于 _exit(0);
举例2:
printf("hello");
exit(0);//运行结束:终端会输出 "hello"
举例3:
printf("hello");
_exit(0);//运行结束:终端不会输出 "hello"


1.return是C语言的关键字,exit、_exit是函数。
2.return是语言级别的,表示调用堆栈的返回。
3._exit是系统调用级别的函数,表示一个进程结束
4.exit函数是C函数库提供的用户级函数
return用于结束一个函数的执行,将函数的执行信息传出,给其他调用函数使用;
exit函数是退出应用程序,删除进程使用的内存空间,并将应用程序的一个状态返回给OS(操作系统),这个状态标识了应用程序的一些运行信息,这个信息和机器和操作系统有关,一般是 0 为正常退出,非0 为非正常退出。
非主函数中调用return和exit效果很明显,但是在main函数中调用return和exit的现象就很模糊,多数情况下现象都是一致的

wait,waitpid

头文件:#include<sys/types.h> ;#include<sys/wait.h>
函数原型:pid_t wait (int * status);
函数参数:int * status : 得到的子进程结束时的状态,也就是exit的实参,退出码必须在0~255之间   如果不想要子进程状态,那 NULL
函数返回值:执行成功则返回子进程PID,如果有错误发生则返回-1。
函数功能:阻塞式等待任意一个子进程结束,并回收子进程空间,避免留下僵尸进程

头文件:#include<sys/types.h>,#include<sys/wait.h>
函数原型:pid_t waitpid(pid_t pid,int * status,int options);
函数参数:
@param1  pid_t pid    :  要等待的子进程PID
@param2  int * status : 得到的子进程结束时的状态,也就是exit的实参,
如果不想要子进程状态,那 NULL
@param3  int options: 等待状态
0                  : 一直等待,直到等到指定子进程---常用
WNOHANG :不予以 等待
函数返回值:执行成功则返回子进程PID,如果有错误发生则返回-1。
函数功能:阻塞式等待指定子进程结束,并回收子进程空间

三、守候进程

守护进程(daemon)称为在后台运行且不受终端控制的进;
常用于提供系统服务或执行定期任务。
怎样让一个进程变为守候进程?
创建守护进程的步骤通常包括:
1.创建子进程,父进程退出(使子进程成为孤儿进程,并被init进程接管)
2.在子进程中创建新会话(setsid)
3.改变当前工作目录到根目录(避免占用可卸载的文件系统)
4.重设文件权限掩码(umask)
5.关闭不需要的文件描述符(特别是标准输入、输出、错误)

四、习题

1.孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程
2.僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程,但僵尸进程无法生成新的进程
  僵尸进程必须使用waitpid/wait接口等待
父进程先于子进程退出,则子进程成为孤儿进程
孤儿进程运行在系统后台

孤儿进程会被以下init系统进程接管
exit函数退出一个进程时会刷新文件缓冲区
_exit
函数退出一个进程时不会刷新文件缓冲区

编程题1:文件共享技术
若在fork()之前打开文件,则父子进程会共享这个文件的读写位置
;若在fork()之后,由父子进程分别打开文件,则在父子进程中文件独立位置是彼此独立的。

#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <string.h>//   ./main    1.txt    2.txt
int main(int argc, char *argv[])
{off_t  size;pid_t  ret1;int    fd1;fd1 = open(argv[1], O_RDWR|O_TRUNC);if(fd1 == -1){printf("file %s was not exist!\n", argv[1]);return -1;}ret1 = fork();//创建子进程if(ret1 == 0){//child only,运行子进程sleep(1); //阻塞1秒size = lseek(fd1, 0, SEEK_CUR);//由于fd1是在fork前执行的,父子共享,此时文件指针为5,当前位置偏移0后的相对于文件开始处绝对偏移量为5字节printf("size = %ld\n", size);//5exit(0);}else{//parent only,一般默认父进程先执行lseek(fd1, 0, SEEK_END);//从文件尾部移动0字节write(fd1,”hello”,5);   //写入了5个字节,文件指针偏移5;wait(NULL);             //等待子进程,代码运行到此处运行子进程exit(0);}
}

2.编程题

编写程序创建一个子进程,父进程打印“parent process”字样和自己的pid;子进程打印“child process”字样以及自己的pid和ppid,并通过exec更改代码段,执行cat命令,cat命令中的参数文件为自己的源程序文件。运行结果截图。注意:运行结果中子进程的ppid是否是父进程的pid?若不是,原因是什么,如何修改程序让两者相同?

不是,因为父进程已经结束,子进程已经变成孤儿进程,被祖先进程init(1)接收修改方式;在父进程中结束前 调用  wait 或 waitpid 函数

#include<sys/types.h> 
#include<sys/wait.h> 
#include <stdlib.h>int main()
{pid_t ret1;ret1=fork();if(ret1<0){printf("创建失败!\n");return -1;}else if(ret1==0)//进入子进程{pid_t childID,parentID;printf("child process\n");childID=getpid();parentID=getppid();printf("childID=%d,parentID=%d\n",childID,parentID);execlp("cat","cat","1.c",NULL);exit(0);}else//进入父进程{pid_t parentID;printf("parent process\n");parentID=getpid();printf("parentID=%d\n",parentID);wait(NULL);exit(0);}
}

 


文章转载自:

http://dqUcIxKD.sqmLw.cn
http://cJOzuATE.sqmLw.cn
http://yTqxE0zN.sqmLw.cn
http://35Ef8DCd.sqmLw.cn
http://lnpZ2iBl.sqmLw.cn
http://GfSsVSvu.sqmLw.cn
http://XpAseDWU.sqmLw.cn
http://hibhrr7T.sqmLw.cn
http://0wjWpA1C.sqmLw.cn
http://wuQW0nzp.sqmLw.cn
http://xKG7fEAh.sqmLw.cn
http://463X7TLM.sqmLw.cn
http://vGE5rDqr.sqmLw.cn
http://ewo9xyte.sqmLw.cn
http://R87xARW1.sqmLw.cn
http://IBdXnHm7.sqmLw.cn
http://WPJQH8mN.sqmLw.cn
http://6HdYi9wK.sqmLw.cn
http://461YlruJ.sqmLw.cn
http://us06l2IX.sqmLw.cn
http://9jrNnbnO.sqmLw.cn
http://Mipkt1iC.sqmLw.cn
http://WHzyTkNT.sqmLw.cn
http://Nk49hSGI.sqmLw.cn
http://sw63G8Am.sqmLw.cn
http://IXYMslpD.sqmLw.cn
http://KzjH3caP.sqmLw.cn
http://gbAImPBH.sqmLw.cn
http://ia6cqBDp.sqmLw.cn
http://0BIsDpc4.sqmLw.cn
http://www.dtcms.com/a/373951.html

相关文章:

  • (Arxiv-2025)MOSAIC:通过对应感知的对齐与解缠实现多主体个性化生成
  • 制造业多数据库整合实战:用 QuickAPI 快速生成 RESTful API 接入 BI 平台
  • outOfMemory内存溢出
  • Pandas数据结构(DataFrame,字典赋值)
  • 谈谈对this的理解
  • CVE-2025-2502 / CNVD-2025-16450 联想电脑管家权限提升漏洞
  • 用 Trae 玩转 Bright Data MCP 集成
  • CiaoTool 批量钱包 多对多转账实战:跨链应用全解析
  • Decision Tree Model|决策树模型
  • 由浅及深:扫描电子显微镜(Scanning Electron Microscope,SEM)
  • CTFHub靶场之SSRF Gopher POST请求(python脚本法)
  • OpenWrt | 在 PPP 拨号模式下启用 IPv6 功能
  • 代码随想录算法训练营第六天 - 哈希表2 || 454.四数相加II / 383.赎金信 / 15.三数之和 / 18.四数之和
  • Java 中 wait 与 notify 的详解:线程协作的关键机制
  • Linux下编译Gmsh
  • api-ms-win-crt-runtime-l1-0.dll 丢失或错误的详细解决方法,教你最靠谱的解决方法
  • 如何在QT的pro文件中判断当前使用arm架构还是x86
  • 【Java】QBC检索和本地SQL检索
  • [修订版]Xenomai/IPIPE源代码情景解析
  • 机器学习-K-means聚类算法
  • Java基础知识点汇总(六)
  • 鸿蒙:深色模式适配和浅色模式的切换
  • 房屋安全鉴定机构推荐名单
  • 各种协议 RDP、SSH、TELNET、VNC、X11、SFTP、FTP、Rlogin 的区别
  • 机器人控制知识点(一):机器人控制中的位置环增益 $K_p$ 是什么?
  • 米勒平台开通和关断过程分析
  • 【ComfyUI】混元3D 2.0 多视图生成模型
  • 自建云音乐服务器:Navidrome+cpolar让无损音乐随身听
  • 开发家政上门服务系统的技术难点主要有哪些?
  • PySpark数据计算