一、进程的概念
当源文件编译通过并且完成链接动作之后,则得到一个可以执行的ELF文件,ELF文件中包含了程序的指令和数据以及相关函数接口的跳转地址等信息,当用户运行程序时,操作系统就会把程序相关的指令和数据载入内存,并为程序分配对应的内存空间,然后通知CPU完成程序的相关动作。
用户编写的源文件是存储在外存(磁盘)中的,而使用编译器生成的ELF可执行文件也是存储在外存中的,这两者都是静态的,操作系统也并没有为两者分配系统资源。
进程定义:进程是一个具有一定独立功能的程序在一个数据集合上依次动态执行的过程。进程是一个正在执行程序的实例。
注意:进程是操作系统分配资源的基本单位!操作系统是以进程为单位来分配系统资源的,比如内存空间、CPU使用权等。 线程是操作系统调度资源的最小单位! 进程包含线程!
二、进程的组成
进程一般由三个部分组成,分别是进程控制块、代码段、数据段。代码段指的是进程中能被调度程序调度到CPU上执行的程序代码段,数据段指的是进程对应的程序原始数据或者程序执行过程中产生的中间数据等。
不同操作系统对于一个进程的信息记录是不同的,也就是PCB中的内容会有一些区别,但是大同小异,基本都会包含以下内容:
1.进程标识符
每个进程都有一个有系统分配的唯一的PID进程标识符(process identifier),用于区分系统中的其他进程,这个PID也是Linux内核提供给用户访问进程的一个接口,用户可以通过PID控制进程。
Linux系统中提供了关于获取进程状态的shell命令:ps ,该命令的使用方法详见man手册,一般使用 ps -ef 或者 ps -aux来查看Linux系统中所有用户相关的进程的所有信息。
2.进程的各状态
A.创建态
指的是进程正在被创建,但是还没有准备好,也就是还没有达到就绪状态,此时系统申请进程PCB需要的内存空间,并向PCB中填写关于进程的管理信息,然后系统分配进程运行需要的资源,最后把进程转换为就绪状态。
B.就绪态
指的是进程已经获得了除了处理器之外的其他资源,相当于“万事俱备,只欠东风”,只要进程获得处理器资源,就可以立即执行。
C.执行态
执行态也被称为运行态,指的是进程已经获得了其他资源和处理器资源,并正在被CPU执行的状态。
D.阻塞态
阻塞态也被称为暂停态,指的是进程在执行过程中由于某个事件导致无法继续执行下去,此时进程处于暂时停止的状态。
E.结束态
指的是进程正在从系统中消失,原因可能是进程正常退出或者由于其他问题中断退出运行。有一种情况比较特殊,就是如果该进程退出之后资源还没有释放,但是此时进程已经无法被调度和运行,进程就会进入僵尸态,此时用户需要对这类进程进行处理,避免占用过多资源。
三、进程的创建
inux系统中的一个进程中可以创建若干个新进程,新创建的进程中又可以创建子进程,所以一般使用进程前趋图来描述创建的进程之间的关系。进程前趋图也被称为进程树,是为了描述进程家族关系的树。
比如在进程A中创建了一个新进程B,则进程B就是进程A的子进程,进程A就是进程B的父进程。
如果在进程A中创建了2个子进程B和C,进程B中创建了2个子进程D和E,进程C中创建了1个子进程F,则进程A就是该进程家族的祖先。
在Linux系统中运行的所有进程也可以构成一个进程树,并且该进程树也有一个祖先,用户可以通过Linux系统提供的shell命令:pstree 来打印进程关系。
可以发现,Linux系统中的所有进程都和一个进程有关系,那就是systemd进程,该进程是Linux系统运行的第一个进程,英文全称是system daemon,中文翻译为系统守护进程,系统中其他进程都是该进程的子进程。
守护进程也被翻译为精灵进程或者后台进程,是一种旨在运行于相对干净环境、不受终端影响的、常驻内存的进程,就像神话中的精灵拥有不死的特性,长期稳定提供某种功能或服务。
systemd其实是一个 Linux 系统基础组件的集合,它提供了一个系统和服务管理器,然后运行为 PID 1的进程,负责在系统启动或运行时激活系统资源,并且管理服务器进程和其它进程。
四、fork函数
Linux系统中只有进程才可以在系统运行,所以一个程序想要运行,就必须为该程序创建进程。linux内核提供了一个名字叫做fork()的系统调用接口,该接口可以在进程中创建一个子进程。
可以看到fork函数的返回值对于父进程和子进程而言是不同的,fork函数在父进程中返回的是创建成功的子进程的PID,fork函数在子进程中的返回值是0,当然,如果子进程失败则返回-1。
进程的撤销
一个进程在完成自身任务之后应该及时撤销,这样就可以及时的释放进程的资源,此时可以分为两种情况:一种是撤销指定进程,另一种是撤销指定进程以及该进程的所有子孙进程。不管是哪种情况,都应该及时回收进程占用的资源。
Linux系统中提供了一个名称叫做wait()的系统调用接口,该接口用于让父进程等待子进程的状态改变并获取已经改变状态的子进程的信息。
僵尸态指的是进程终止但是并未释放相关资源的一种状态,此时由系统内核对处于僵尸态的进程进行维护,处于僵尸态的进程会占用创建进程的资源和数量,如果僵尸态进程数量太多,则会导致系统无法创建新进程。
所以父进程应该及时的对终止的子进程进行等待,这样就可以回收子进程占用的资源,当然,如果父进程终止,则处于僵尸态的子进程会由系统内核完成资源的回收。
五、练习
1.
编写程序,要求在程序中创建一个子进程,让父进程和子进程分别打印不同的内容。
/*
* Copyright (c)
*
* date: 2025-7-29
*
* author: Charles
*
* function name : fork_test.c
*
* function: 编写程序,要求在程序中创建一个子进程,让父进程和子进程分别打印不同的内容。
*
*/
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
pid_t child_pid = fork();
if(child_pid == 0){
printf("this child pid: %d, parent pid: %d\n",getpid(), getppid());
}else if(child_pid > 0){
printf("this parent pid: %d, child pid: %d\n", getpid(), child_pid);
while(1);
}
else {
printf("fork fail!\n");
exit(-1);
}
return 0;
}
2.
编程产生一个进程链,父进程派生一个子进程后,输出自己的PID,然后退出,该子进程继续派生子进程,然后打印PID,然后退出,以此类推,要求父进程比子进程先输出PID,另外进程链的数量由用户通过键盘输入。
/*
* Copyright (c)
*
* date: 2025-7-29
*
* author: Charles
*
* function name : ProcessLink.c
*
* function: 编程产生一个进程链,父进程派生一个子进程后,输出自己的PID,然后退出,
*该子进程继续派生子进程,然后打印PID,然后退出,以此类推,要求父进程比子进程先输出PID,另外进程链的数量由用户通过键盘输入。
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc, char const *argv[])
{
int num = 0;
scanf("%d", &num);
if(num <= 0){
sprintf(stderr, "input num less than zero!\n");
exit(EXIT_FAILURE);
}
for(int i = 1; i < num; i++){
pid_t child_pid = fork();
if(child_pid > 0){
printf("my PID:%d, child PID:%d\n",getpid(), child_pid);
wait(NULL);
exit(EXIT_SUCCESS);
}
else if(child_pid == 0){
printf("chlid PID:%d, is %d num\n", getpid(), i + 1);
sleep(1);
}
else{
perror("fork fail\n");
exit(EXIT_FAILURE);
}
}
return 0;
}
3.
编写一个程序,使之产生一个子进程,在子进程中执行系统命令ls -l去查看某个文件的信息,父进程判断子进程是否执行成功。要求父进程等待子进程结束之后再结束。提示:Linux系统中的shell命令属于可执行文件
/*
* Copyright (c)
*
* date: 2025-7-29
*
* author: Charles
*
* function name : ForkSys.c
*
* function: 编写一个程序,使之产生一个子进程,在子进程中执行系统命令ls -l去查看某个文件的信息,
# 父进程判断子进程是否执行成功。要求父进程等待子进程结束之后再结束。提示:Linux系统中的shell命令属于可执行文件
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc, char const *argv[])
{
if(argc != 2){
printf("argc must 2\n");
exit(EXIT_FAILURE);
}
pid_t pid = fork();
char command[128] = {0};
snprintf(command, sizeof(command), "ls -l %s", argv[1]);
if(pid > 0){
printf("parent pid:%d\n", getpid());
wait(NULL);
}
else if(pid == 0){
printf("child pid:%d\n", getpid());
system(command);
}
else{
perror("fork fail!\n");
exit(EXIT_FAILURE);
}
return 0;
}
希望各位靓仔靓女点赞,收藏,关注多多支持,我们共同进步,后续我会更新更多的面试真题,你们的支持将是我前进最大的动力。