LInux ---- 多进程
头文件 #include <sys/types.h> #include <unistd.h>
函数 pid_t fork(void); 函数的作用:用于创建子进程。 返回值:类型:pid_t fork()的返回值会返回两次。一次是在父进程中,一次是在子进程中。 在父进程中返回创建的子进程的ID, 在子进程中返回0
在父进程中返回-1,表示创建子进程失败,并且设置errno
如何区分父进程和子进程:通过fork的返回值。 父子进程之间的关系: 区别: 1.fork()函数的返回值不同 父进程中: >0 返回的子进程的ID 子进程中: =0 2.pcb中的一些数据 当前进程的id pid 当前进程的父进程的id ppid 信号集 共同点: 某些状态下:子进程刚被创建出来,还没有执行任何的写数据的操作 - 用户区的数据 - 文件描述符表 父子进程对变量是不是共享的? - 刚开始的时候,是一样的,共享的。如果修改了数据,不共享了。 - 读时共享(子进程被创建,两个进程没有做任何写操作),写时拷贝。
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main() {
6
7 int num = 10;
8
9 // 创建子进程
10 pid_t pid = fork();
11
12 // 判断是父进程还是子进程
13 if (pid > 0) {
14 // printf("pid : %d\n", pid);
15 // 如果大于0,返回的是创建的子进程的进程号,当前是父进程
16 printf("i am parent process, pid : %d, ppid : %d\n", getpid(), getppid());
17
18 printf("parent num : %d\n", num);
19 num += 10;
20 printf("parent num += 10 : %d\n", num);
21 }
22 else if (pid == 0) {
23 // 当前是子进程
24 printf("i am child process, pid : %d, ppid : %d\n", getpid(), getppid());
25
26 printf("child num : %d\n", num);
27 num += 100;
28 printf("child num += 100 : %d\n", num);
29 }
30
31 // for循环
32 for (int i = 0; i < 3; i++) {
33 printf("i : %d , pid : %d\n", i, getpid());
34 sleep(1);
35 }
36
37 return 0;
38 }
实际上,更准确来说,Linux 的 fork() 使用是通过写时拷贝 (copy- on-write) 实现。写时拷贝是一种可以推迟甚至避免拷贝数据的技术。内核此时并不复制整个进程的地址空间,而是让父子进程共享同一个地址空间。只用在需要写入的时候才会复制地址空间,从而使各个进行拥有各自的地址空间。也就是说,资源的复制是在需要写入的时候才会进行,在此之前,只有以只读方式共享。
注意:fork之后父子进程共享文件,fork产生的子进程与父进程相同的文件文件描述符指向相同的文件表,引用计数增加,共享文件偏移指针。
GDB调试
使用 GDB 调试的时候,GDB 默认只能跟踪一个进程,可以在 fork 函数调用之前,通 过指令设置 GDB 调试工具跟踪父进程或者是跟踪子进程,默认跟踪父进程。
设置调试父进程或者子进程:set follow-fork-mode [parent(默认)| child]
设置调试模式:set detach-on-fork [on | off]
默认为 on,表示调试当前进程的时候,其它的进程继续运行,如果为 off,调试当前进程的时候,其它进程被 GDB 挂起。
查看调试的进程:info inferiors
切换当前调试的进程:inferior id
使进程脱离 GDB 调试:detach inferiors id
在父进程中返回-1,表示创建子进程失败,并且设置errno
exce函数族
在进程的创建上Unix采用了一个独特的方法,它将进程创建与加载一个新进程映象分离。这样的好处是有更多的余地对两种操作进行管理。当我们创建了一个进程之后,通常将子进程替换成新的进程映象,这可以用exec系列的函数来进行。当然,exec系列的函数也可以将当前进程替换掉。
例如:在shell命令行执行ps命令,实际上是shell进程调用fork复制一个新的子进程,在利用exec系统调用将新产生的子进程完全替换成ps进程。
exec 函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的 内容,换句话说,就是在调用进程内部执行一个可执行文件。
exec 函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程 ID 等一些表面上的信息仍保持原样, 颇有些神似“三十六计”中的“金蝉脱壳”。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回-1,从原程序的调用点接着往下执行。
#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main() { // 创建一个子进程,在子进程中执行exec函数族中的函数 pid_t pid = fork(); if (pid > 0) { // 父进程 printf("i am parent process, pid : %d\n", getpid()); sleep(1); } else if (pid == 0) { // 子进程 execl("hello", "hello", NULL); //第一个hello为相对路径,即当前文件夹内 execlp("ps", "ps", "aux", NULL); //环境变量中有ps的路径 execl("/bin/ps", "ps", "aux", NULL); perror("execl"); printf("i am child process, pid : %d\n", getpid()); } for (int i = 0; i < 3; i++) { printf("i = %d, pid = %d\n", i, getpid()); } return 0; }
◼ 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[]);
◼ int execve(const char *filename, char *const argv[], char *const envp[]);
l(list) 参数地址列表,以空指针结尾
v(vector) 存有各参数地址的指针数组的地址
p(path) 按 PATH 环境变量指定的目录搜索可执行文件
e(environment) 存有环境变量字符串地址的指针数组的地址
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)