- (꒪ꇴ꒪ ),Hello我是祐言QAQ
- 我的博客主页:C/C++语言,Linux基础,ARM开发板,软件配置等领域博主🌍
- 快上🚘,一起学习,让我们成为一个强大的攻城狮!
- 送给自己和读者的一句鸡汤🤔:集中起来的意志可以击穿顽石!
- 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
一、进程的概念
1.1 概念
进程是操作系统中的核心概念之一,它代表了一个正在执行的程序实例。每个进程都有自己独立的内存空间,包括代码、数据和执行上下文。进程能够并发地执行,它是操作系统中多任务处理(资源分配)的基本单位。
1.2 进程控制块(PCB)
进程控制块是操作系统内部用来管理进程的数据结构。它包含了进程的各种信息,如进程状态、程序计数器、寄存器状态、内存分配情况等。PCB允许操作系统在不同进程之间切换,从而实现多任务处理。
二、进程的组织方式
2.1 父子进程关系
进程可以通过父子关系来组织。一个进程可以创建新的子进程,子进程与父进程共享某些资源。子进程在创建后可以独立运行,拥有自己的进程ID。此外还拥有如下的一些特点:
(1)查看进程树,以树形图显示进程: pstree;
(2)除了系统初始init进程以外,任何进程都必须有父进程;
(3)如果一个进程的父进程死掉,那么这个进程就会被系统分配一个默认父进程给它。
2.2 进程运行
进程可以在不同的状态之间切换,如运行状态、就绪状态和阻塞状态。操作系统的调度算法决定了哪个进程在特定时间片段内执行。
(1)前台进程: ./程序文件名;
(2)后台进程: ./程序文件名 &;
(3)前台进程可以直接用ctrl + c关闭,后台进程不行,我们可以用 kill <进程号>关闭该进程。
值得注意的是:linux系统中每一个进程都有父进程,当这个进程的父进程死掉,子进程如果没有跟着父进程一起死掉就变成孤儿进程,但是linux不允许,它会将这个进程作为一个特殊进程(类似孤儿院)的子进程。
2.3 查看正在运行的进程
操作系统提供了工具来查看当前正在运行的进程,如ps
命令。这可以帮助您监控系统中正在执行的任务。一些常用的后缀项如下:
-f 查看更多信息;
-u 显示更多信息(和f显示的信息不一样);
-a 显示所有用户的所有进程;
-x 显示无终端的进程;
-e 显示所有进程;
ps -ef 显示系统中所有的进程以及相关的详细信息;
2.4 终止进程
进程可以通过发送终止信号来结束自己的执行,或者被其他进程终止。终止时,操作系统会释放进程占用的资源。常用命令为:
kill <进程号>
kill -9 <进程号>
2.5 动态显示正在运行的进程
通过周期性地查询进程信息并显示在屏幕上,您可以实时监控系统中正在运行的进程,了解系统负载情况。(类似Windows的任务管理器),使用的命令为top。
top
是一个强大的工具,可以帮助您监控系统性能、查看进程信息以及定位资源瓶颈。通过实时的信息更新,可以更好地了解系统的运行情况。如有需要,您还可以通过命令行中的帮助文档(man top
)来了解更多使用选项。
该界面提供了一些重要的信息:
Tasks
: 当前运行的任务数,包括运行中、睡眠中、停止等状态的任务;%Cpu(s)
: CPU的使用率分布,包括用户态、系统态、空闲等;KiB Mem
: 内存使用情况,包括总内存、空闲内存、已使用内存和缓存;KiB Swap
: 交换空间使用情况,包括总交换空间、空闲交换空间和已使用交换空间。
如果你想退出呢, 可以使用 q
键来退出 top
命令,或者使用 Ctrl+C
组合键来终止命令的执行。
三、 进程的状态
进程的状态包括执行太、暂停态、僵尸态等。了解这些状态可以帮助理解进程的行为以及操作系统是如何管理它们的。
四、 进程相关函数
4.1 获取进程ID和父进程ID
通过调用相应的函数,您可以获取当前进程的ID以及父进程的ID。
#include <unistd.h>
#include <sys/types.h>
pid_t getpid(void);//获取当前进程的进程号
pid_t getppid(void);//获取当前进程的父进程号
代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main() {
pid_t child1, child2;
child1 = fork(); // 创建子进程1
if (child1 == 0) {
// 子进程1
printf("子进程1 - PID: %d, 其父进程PID: %d\n", getpid(), getppid());
child2 = fork(); // 在子进程1中创建子进程2
if (child2 == 0) {
// 子进程2
printf("子进程2 - PID: %d, 其父进程PID: %d\n", getpid(), getppid());
}
}
return 0;
}
4.2 创建进程(fork)
fork
函数用于创建一个新的子进程,子进程是父进程的副本,包括内存内容和程序计数器。
#include <unistd.h>
pid_t fork(void);
返回值:
在父进程中,返回值大于0,表示创建的子进程的进程号
在子进程中,返回值等于0
进程创建失败,返回值等于-1,并带有errno
备注:该函数执行成功之后,将会产生一个新的子进程,在新的子进程中其返回值为 0,在原来的父进程中其返回值为大于 0 的正整数,该正整数就是子进程的 PID
另一个创建进程的函数:
#include <sys/types.h>
#include <unistd.h>pid_t vfork(void);
返回值:
在父进程中,返回值大于0,表示创建的子进程的进程号
在子进程中,返回值等于0
进程创建失败,返回值等于-1,并带有errno
区别:
fork:
1、函数调用之后,在下一行父子进程执行的时候,父进程比子进程先执行,但是不是百分之百,
2、父子进程不共享内存,子进程更改数据,父进程不会受影响
vfork:
1、函数调用之后,在下一行父子进程执行的时候,子进程永远比父进程先执行,子进程要有退出,父进程才会执行
2、父子进程共享内存,子进程更改数据,父进程会受影响
4.3 exec函数簇
exec
函数簇允许一个进程加载并运行一个新的程序。这使得进程能够动态改变执行的代码。
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
int execv(const char *path, char *const argv[ ]);
int execle(const char *path, const char *arg, ..., char * const envp[ ]);int execlp(const char *file, const char *arg, ...);
int execvp(const char *file, char *const argv[ ]);
int execvpe(const char *file, char *const argv[ ],char *const envp[ ]);参数列表:
path 即将被加载执行的 ELF 文件或脚本的路径;
file 即将被加载执行的 ELF 文件或脚本的名字;
arg 以列表方式罗列的 ELF 文件或脚本的参数;
argv 以数组方式组织的 ELF 文件或脚本的参数;
envp 用户自定义的环境变量数组。
备注:
1.函数名带字母 l 意味着其参数以列表(list)的方式提供;
2.函数名带字母 v 意味着其参数以矢量(vector)数组的方式提供;
3.函数名带字母 p 意味着会利用环境变量 PATH 来找寻指定的执行文件;
4.函数名带字母 e 意味着用户提供自定义的环境变量。
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
printf("进程开始运行\n");
char *ar[] = {"./hello", NULL};
pid_t x = fork();//调用fork创建子进程,执行成功之后,在下一行开始有一个父进程和子进程,两个进程
if (x == -1)
{
perror("fork");
}
if (x == 0)
{
//让子进程执行当前路径下的hello
execv("./hello", ar);
// execvp("./hello", ar);
}
if (x > 0)
{
printf("父进程正在运行\n");
sleep(1);
printf("父进程结束运行\n");
}
return 0;
}
4.4 程序退出
进程可以通过调用exit
函数来正常退出,释放其占用的资源。另外,进程也可以通过异常或信号被强制终止。
(1)return 关键字:函数退出,当return放在main函数的时候,也表示程序退出。
(2)exit() 函数
#include <stdlib.h>
void exit(int status);
参数列表: status 子进程的退出值
(3)_exit()函数
#include <unistd.h>
void _exit(int status);
参数列表: status 子进程的退出值
值得注意的是:
1.如果子进程正常退出,则 status 一般为 0;
2.如果子进程异常退出,则 statuc 一般为非 0;
3.exit()退出时,会自动冲洗(flush)标准 IO 总残留的数据到内核;
4.如果进程注册 了“退出处理函数”还会自动执行这些函数。而_exit( )会直接退出。
(4) 程序退出处理函数注册
在 <stdlib.h>
头文件中,提供了 atexit
函数,用于注册在程序退出时执行的函数。函数原型如下:
int atexit(void (*function)(void));
参数 function
是一个指向返回类型为 void
,参数列表为空的函数的指针。当程序退出时,注册的函数会被调用,它可以用来执行清理操作,释放资源等。
4.5 等待子进程退出/状态变化
在 <sys/wait.h>
头文件中,提供了两个用于等待子进程退出或状态变化的函数:wait
和 waitpid
。
(1)wait 函数:
pid_t wait(int *stat_loc);
参数
stat_loc
是一个指向整数的指针,用于存储子进程的退出状态。
(2)waitpid 函数:
pid_t waitpid(pid_t pid, int *stat_loc, int options);
pid
:等待的目标进程ID。根据不同值的含义,可以等待不同的子进程。stat_loc
:一个指向整数的指针,用于存储子进程的退出状态。options
:附加选项,例如使用WNOHANG
可以使函数变为非阻塞。
这两个函数在等待子进程退出或状态变化时非常有用,可以帮助父进程处理子进程的生命周期。返回值为子进程的ID,表示成功等待到子进程的状态变化。如果设置了 WNOHANG
选项且状态尚未改变,则 waitpid
可以返回0。如果失败,返回-1。
五、同步与互斥
在多进程编程中,同步(Synchronization)和互斥(Mutual Exclusion)是两个重要的概念,用于确保多个进程之间的正确协作和资源访问。
1.同步(Synchronization)
同步是指多个进程之间通过某种机制来协调它们的行为,以便按照期望的顺序执行,避免产生竞争条件和不一致性。同步的目的是为了确保进程间的协作和有序执行,以避免不确定性和数据损坏。
在多进程编程中,可以使用各种同步机制来实现同步,如信号量、互斥锁、条件变量等。这些机制可以确保进程按照特定的顺序执行,以达到预期的结果。
2.互斥(Mutual Exclusion)
互斥是一种同步机制,用于确保在同一时刻只有一个进程可以访问共享资源。这意味着在一个进程访问共享资源时,其他进程必须等待,直到该进程释放资源。
互斥通常通过互斥锁(Mutex)来实现。当一个进程获取了互斥锁后,其他进程必须等待,直到该进程释放锁。这样可以避免多个进程同时修改共享资源导致数据不一致的问题。
更多C语言、Linux系统、ARM板实战和数据结构相关文章,关注专栏:
手撕C语言
玩转linux
脚踢数据结构
6818(ARM)开发板实战
📢写在最后
- 今天的分享就到这啦~
- 觉得博主写的还不错的烦劳
一键三连喔
~ - 🎉感谢关注🎉