Linux——进程编程(一)进程的创建:fork 、exec
一、创建进程——fork函数
fork函数创建一个子进程,父子进程执行同样的程序。
fork意思是叉子,可以这样理解:fork之前,程序是顺序结构。如同叉子的杆;fork之后程序有了分叉,如同叉子的叉
1、用法
头文件:
#include <sys/types.h>
#include <unistd.h>
原型:
pid_t fork(void);
2、返回值、返回类型
C语言中,一个函数至多有一个返回值。fork后进程变为两个,各自有一个fork函数,父子进程中的fork各自有返回。
父进程中,若创建成功,则fork返回子进程的ID值;若失败,则返回 -1;
子进程中,若创建成功,则fork返回0;(进程号从1开始记,0不是有效进程号,所以0可以用于标识自己)
pid_t 类型:因为进程号为非负整数,且失败返回值 -1,所以pid_t必须是有符号整数。在不同系统中长度不同,一般为 short 或者 int 型 ,<sys/types.h>头文件会自动判断。
3、父子进程共享
刚 fork 之后,父子进程异同:
父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户 ID、宿主目录、进程工作目录、信号处理方式...
父子不同处: 1.进程 ID 、2.fork 返回值 、3.父进程 ID 、4.进程运行时间 、5.闹钟(定时器) 、6.未决信号集
似乎,子进程复制了父进程 0-3G 用户空间内容,以及父进程的 PCB(但 pid 不同) 。每 fork 一个子进程都要将父进程的 0-3G 地址空间完全拷贝一份,然后在映射至物理内存吗?当然不是!父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
4、进程ID(PID)
pid即进程id,从1开始记,1为init进程。使用 ps 命令可以查看进程。
pid相关函数:
- pid_t getpid(void); 获取当前进程的pid
- pid_t getppid(void); 获取当前进程的父进程的pid
其他类似函数:
- uid_t getuid(void); 获取当前进程实际用户id
- uid_t geteuid(void); 获取当前进程有效用户id
- gid_t getgid(void); 获取当前进程使用用户组id
- gid_t getegid(void); 获取当前进程有效用户组id
5、例子
#include<stdio.h>
#include<unistd.h>
int main(int argc, char argv[]) {
printf("\n p/c fork() return pid ppid\n");
pid_t fpid = fork();
if(fpid < 0){
printf("Error: %d\n",fpid);
}
else if(fpid == 0){
printf("child %d %d %d\n\n", fpid, getpid(), getppid());
}
else if(fpid > 0){
printf("parent %d %d %d\n", fpid, getpid(), getppid());
}
return 0;
}
结果:
非预期结果:
造成非预期结果的原因:
父进程先于子进程结束,子进程成为孤儿进程,子进程的父进程变为init下的/lib/systemd/systemd --user,即进程孤儿院
二、指定任务——exec函数族
六种以exec开头的函数,统称exec函数。exec让当前进程执行指定的程序,通常fork之后在子进程中使用。exec 函数一旦调用成功即执行新的程序,不返回。只有失败才返回 -1。所以通常直接在 exec 函数调用后直接调用 perror()和 exit(),无需 if 判断。
1、exec函数作用
fork 创建子进程后执行的是和父进程相同的程序,子进程往往要调用一种 exec 函数以执行另一个程序。当进程调用一种 exec 函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用 exec 并不创建新进程,所以调用 exec 前后该进程的 id 并未改变,.text、.data替换为将要加载的程序的.text、.data,然后让进程从.text的第一条指令开始执行。换核不换壳。
2、exec函数命名特点
6个函数都是在exec的基础上加上几个字母作为函数名,添加字母和对应含义如下:
- l (list) 命令行参数列表
- p (path) 搜素file时使用path变量
- v (vector) 使用命令行参数数组
- e (environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量
3、exec函数族成员
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);
只有 execve 是真正的系统调用,其它五个函数最终都调用 execve,所以 execve在 man 手册第 2 节,其它函数在 man 手册第 3 节。
这些函数之间的关系如下图所示。
具体用法可自行查阅man手册或google
man execve
4、重点掌握
- execlp 从PATH环境变量寻找待执行程序
- execl 自己指定待执行程序的路径
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理