嵌入式linux基础:进程
什么是进程?
其实在 Linux 操作系统中,进程是程序的一个执行实例,也就是说程序运行后,操作系统会给程序分配独立地址空间、系统资源(如文件描述符、内存、CPU 时间等)和状态信息,可以简单理解为正在运行的程序,在Linux中就是进程。
简单理解:
程序 = 静态的代码文件
进程 = 动态运行中的程序实例
例如:
我们编写了一个 hello 程序。
当你在终端输入 ./hello 时,操作系统会创建一个新的执行实体来运行它,这个执行实体就是进程。
程序和进程的区别
| 对比项 | 程序 | 进程 |
|---|---|---|
| 性质 | 静态的 | 动态的 |
| 存在形式 | 磁盘上的可执行文件 | 内存中正在运行的实例 |
| 内容 | 指令和数据的集合 | 程序代码 + 数据 + 运行状态 + 系统资源 |
| 生命周期 | 长期保存,直到被删除 | 有创建、运行、阻塞、结束等过程 |
| 数量关系 | 一个程序可以被多次执行 | 一个程序可以对应多个进程 |
| 资源占用 | 不占用运行时资源 | 会占用 CPU、内存、文件描述符等资源 |
为什么需要进程
因为他有很多特点可以使用
| 作用 | 说明 |
|---|---|
| 提高安全性 | 一个进程出错,一般不会直接影响其他进程 |
| 提高效率 | 多个进程并发执行,可以提高系统吞吐量和响应速度 |
| 简化编程 | 可以把复杂任务拆成多个进程分别处理 |
| 支持多任务 | 操作系统可以同时运行多个程序 |
进程的基本特点
| 特点 | 含义 |
|---|---|
| 动态性 | 进程是程序运行后的状态,会不断变化 |
| 独立性 | 每个进程通常有自己独立的地址空间和资源 |
| 并发性 | 多个进程可以在同一时间段内同时推进执行 |
| 异步性 | 进程的执行顺序和速度不完全确定,受调度影响 |
进程在项目中有哪些应用场景呢?
这里句两个例子
保活进程:主业务逻辑作为单独进程运行,另外开发一个精简的进程用于监控主任务进程,一旦发现主任务进程挂掉或在无应答,那就进行重启&日志记录。

简化编程:例如我们要做一个大型项目,里面有网络部分,显示部分,设备控制部分,我们可以单独把这三个模块设计为三个单独的进程运行,然后通过进程通信交换状态即可,这样无论是开发、维护都更加方便。
#include <stdio.h>
int main() {
printf("Hello, World!\n");
while(1);
return 0;
}
我们运行他
./hello &
运行后,通过ps指令,可以看到这个程序在运行,93899就是进程的id
leo@ubuntu:~/hello/build$ ps
PID TTY TIME CMD
84398 pts/7 00:00:00 bash
93899 pts/7 00:06:30 hello
如果我们需要关闭进程,我们可以使用
kill -9 93899
其中,-9表示发送强制终止的信号。
Linux的父子进程
Ubuntu内核在启动时,会创建第一个pid为1的进程,后续其它进程都是由此进程创建和管理(当然除了僵尸进程),所以我们可以理解Linux其实把所有进程都放在一个树状的结构体中进行管理,这点我们可以执行命令pstree看到
leo@ubuntu:~/hello/build$./hello &
leo@ubuntu:~/hello/build$ pstree
systemd─┬─ModemManager───2*[{ModemManager}]
├─NetworkManager─┬─dhclient
│ └─2*[{NetworkManager}]
├─VGAuthService
├─accounts-daemon───2*[{accounts-daemon}]
├─acpid
├─adb───4*[{adb}]
....
│ ├─gnome-terminal-─┬─bash─┬─hello
│ │ │ └─pstree
在这里,我们可以认为命令行窗口bash就是父进程,hello就是子进程,因为我们是在命令行窗口执行hello的。
| 概念 | 说明 |
|---|---|
| 父进程 | 创建其他进程的进程 |
| 子进程 | 被创建出来的新进程 |
| 类型 | 含义 | 产生原因 | 处理方式 |
|---|---|---|---|
| 僵尸进程 | 子进程已经结束,但其退出信息还没被父进程回收 | 父进程没有调用 wait() 或 waitpid() |
父进程及时回收 |
| 孤儿进程 | 父进程先结束,子进程还在运行 | 父进程提前退出 | 会被 1 号进程接管 |
接下来,我们来看看一个具体的项目需求,在做一些比较大型的项目时,我们一般会把一个项目的功能模块拆分为多个子应用,假设我们有这么一个需求,我们把一个项目分为两个程序,然后通过一个保活程序对另一个程序进行管理,包括启动,监听运行状态和重启,这时候我们应该如何实现呢?
想要实现这几个功能,我们就需要学习下如何通过程序启动另一个进程,如何对进程进行控制,接下来,我们来看看进程相关的管理函数。
进程创建的方式
fork函数
#include <unistd.h>
pid_t fork(void);
功能:fork 系统调用用于创建一个新的进程,新进程是原进程的一个副本。
子进程特性:
子进程拥有与父进程相同的代码段、数据段、堆栈段等。
子进程有自己的独立内存空间,但初始内容与父进程相同。
子进程从 fork 调用后的下一条指令开始执行。
返回值:
在父进程中返回子进程的 PID。
在子进程中返回 0。
失败时返回-1
通过fork的返回值区分父进程和子进程
示例代码如下
#include <stdio.h>
#include <unistd.h>
int main(void)
{
int result;
printf("This is a fork demo!\n\n");
/*调用 fork()函数*/
result = fork();
if(result == -1) {
/*通过 result 的值来判断 fork()函数的返回情况,首先进行出错处理*/
printf("Fork error\n");
return -1;
}
else if (result == 0) {
/*返回值为 0 代表子进程*/
printf("The returned value is %d, In child process!! My PID is %d\n\n", result, getpid());
}
else {
/*返回值大于 0 代表父进程 resutl为子进程pid*/
printf("The returned value is %d, In father process!! My PID is %d\n\n", result, getpid());
}
while(1);
return result;
}
然后,我们就可以通过ps查看运行后的进程情况
leo@ubuntu:~/hello/build$./hello &
leo@ubuntu:~/hello/build$ ps
PID TTY TIME CMD
2308 pts/0 00:00:00 bash
2637 pts/0 00:00:17 hello
2638 pts/0 00:00:17 hello
进程的终止方式
进程有两种终止方式,正常终止和异常终止。
正常终止:
1、进程调用return
int main() {
return 0;
}
2、进程调用 exit 函数
#include <stdlib.h>
void exit(int status);
status:进程退出状态码,是进程终止时向父进程传递的一个整数值(范围为 0~255)。它的核心作用是标识进程的执行结果状态,便于父进程或调用者判断进程是否正常结束、是否发生错误。
void func() {
exit(1); // 在非main函数中调用,进程直接退出
}
异常终止:
进程收到某些信号(如 SIGKILL、SIGSEGV)而终止。 例如前面的kill -9 93899
进程的状态监控
#include <sys/wait.h> pid_t waitpid(pid_t pid, int *status, int options); 父进程等待子进程终止。 pid:指定等待的子进程ID: > 0:等待 PID 等于该值的子进程。 -1:等待任意子进程(等同于 wait())。 status:同 wait(),存储子进程退出状态。 options:位掩码,常用选项: 0:阻塞等待指定子进程终止。 WNOHANG:非阻塞模式,若无子进程终止立即返回 0。 返回值: > 0成功返回结束的子进程PID。 0 非阻塞模式(WNOHANG)下,没有子进程退出。 -1错误(如无子进程、信号中断等),通过 errno 获取具体原因。 #include <sys/wait.h> pid_t wait(int *status); 等待任意一个子进程结束。 status:同 wait(),存储子进程退出状态。
例如,父进程等待子进程终止后,再执行
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
int pid;
printf("This is a fork demo!\n\n");
/*调用 fork()函数*/
pid = fork();
if(pid == -1) {
/*通过 pid 的值来判断 fork()函数的返回情况,首先进行出错处理*/
printf("Fork error\n");
return -1;
}
else if (pid == 0) {
/*返回值为 0 代表子进程*/
printf("The returned value is %d, In child process!! My PID is %d\n\n", pid, getpid());
return 1;
}
else {
/*返回值大于 0 代表父进程 resutl为子进程pid*/
int status;
waitpid(pid,&status,0);//阻塞等待子进程运行完成或直接调用wait(status)
printf("child return status = %d\n",WEXITSTATUS(status));
printf("The returned value is %d, In father process!! My PID is %d\n\n", pid, getpid());
}
return 0;
}
在上面的代码中,我们可以看到,虽然我们通过fork函数创建了子进程,但是这两个进程其实是一样的,相当于子进程是父进程的复制。如果我们希望创建一个新进程,执行的是别的任务,如何创建呢?那就需要用到exec函数。
exec 系列函数:在现有进程中加载并执行新的程序。
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
功能:execl 系列函数用于在当前进程中加载并执行新的程序。
参数:
path: 可执行文件的绝对路径。
arg: 可执行程序的参数列表,第一个参数通常是可执行文件的名字,最后一个参数必须是 NULL。
arg0(程序名称):通常是程序本身的名字,会作为 argv[0] 传递给新程序。
例如:execl("/bin/ls", "ls", "-l", NULL) 中的 "ls"。
后续参数(arg1, arg2, ..., NULL):从 arg1 开始是真正的命令行参数。
必须以 (char *) NULL 结尾,否则会导致未定义行为。
返回值:
成功时无返回值。
失败时返回 -1,并设置 errno。
比如
#include <stdio.h>
#include <unistd.h>
int main() {
// 执行ls -l /home
execl("/bin/ls", "ls", "-l","/home", NULL);
return 0;
}
如何创建一个子进程并执行新的程序:
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main() {
pid_t pid = fork();
if (pid == -1) {
printf("fork error");
return 1;
} else if (pid == 0) {
// 子进程
printf("Child process: PID = %d\n", getpid());
if (execl("/bin/ls", "ls", "-l", "/home/xi", NULL) < 0)//执行这个ls程序
{
printf("execlp error");
return 1;
}
} else {
// 父进程
printf("Parent process: PID = %d, Child PID = %d\n", getpid(), pid);
int status;
waitpid(pid, &status, 0);
printf("Child exited with status %d\n", WEXITSTATUS(status));
}
return 0;
}
实现一个保活进程,对另外的进程进行启动和监控。代码如下
//monitor程序
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#define PROGRAM1 "./hello"//需要的可执行文件
pid_t pid;
void start_program(const char *program) {
pid = fork();
if (pid == 0) {
// 子进程
execl(program, program, (char *)NULL);
printf("execl error"); // 如果 execl 失败
exit(1);
} else if (pid < 0) {
printf("fork error");
exit(1);
}
}
void monitor_programs() {
while (1) {
int status;
pid_t result;
usleep(10*1000);
// 检查子进程是否已经退出
result = waitpid(pid, &status, WNOHANG);
if (result == 0) {
// 返回=0意味着子进程仍在运行
} else if (result == -1) {
printf("waitpid error");
exit(1);
} else {
// 返回>0意味子进程已退出
printf("restart process\n");
//加延时方便观察
usleep(2*1000);
// 重新启动子进程
start_program(PROGRAM1);
}
}
}
int main() {
// 启动程序
start_program(PROGRAM1);
printf("start %s\n", PROGRAM1);
// 监控程序
monitor_programs();
return 0;
}
最后把monitor、hello放到同一个路径下,运行monitor,我们就可以看到monitor启动了hello进程,日志如下:
Started ./hello with PID 7207
hello
hello
这时,我们可以用kill -9 指令,杀掉进程hello,然后就可以看到monitor会监控它的状态,并重新启动它。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)