资料合集
链接:https://pan.quark.cn/s/770d9387db5f
在我们上一篇关于 的探索中,我们学会了如何创造新进程。但每个进程并非凭空而来,它都有自己的“父母”和“祖先”。今天,我们将扮演一次“进程侦探”,从一个简单的函数
fork() 开始,一步步揭开进程的身世之谜,绘制出壮观的进程家族谱,并最终触及操作系统最核心的概念之一:系统调用。
getppid()
一、 我是谁?我的父亲是谁?——
getpid() 与
getppid()
getpid()
getppid()
每个进程都有一个独一无二的身份证号——PID (Process ID)。我们可以通过 函数获取自己的PID。但一个进程不仅要知道自己是谁,还要知道自己从哪里来。
getpid() 函数(第一个'p'代表parent)就是用来寻找“父进程”的。
getppid()
// 获取当前进程ID
pid_t getpid(void); // 获取当前进程的父进程ID
pid_t getppid(void);
【实验一:父子进程的“亲子鉴定”】 让我们修改之前的 代码,让父子进程都打印出自己的PID和父进程的PID,看看会发生什么。
fork()
代码 ()
process_ancestry.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main() {
pid_t pid;
printf("--- Before fork ---
");
printf("I am the original process. My PID is %d, and my parent's PID is %d.
", getpid(), getppid());
printf("---------------------
");
pid = fork();
if (pid < 0) {
perror("fork error");
return 1;
} else if (pid == 0) {
// 子进程的世界
printf("I am the CHILD. My PID is %d, and my parent's PID is %d.
", getpid(), getppid());
} else {
// 父进程的世界
// sleep(1); // 暂时注释掉,观察随机执行顺序
printf("I am the PARENT. My PID is %d, and my parent's PID is %d.
", getpid(), getppid());
wait(NULL);
}
return 0;
}
编译与运行
gcc process_ancestry.c -o ancestry
./ancestry
运行结果 (PID会变,但关系不变)
--- Before fork ---
I am the original process. My PID is 38120, and my parent's PID is 37580.
---------------------
I am the PARENT. My PID is 38120, and my parent's PID is 37580.
I am the CHILD. My PID is 38121, and my parent's PID is 38120.
结果分析:
子进程的“亲子鉴定”:子进程(PID )调用
38121 得到的结果是
getppid(),这正是父进程的PID!亲子关系确认无误。父进程的“身世”:父进程(PID
38120)的父进程是
38120。这个
37580 是谁?它就是我们正在使用的终端程序,比如
37580。执行顺序问题:你可能会遇到终端提示符(
bash)插在输出中间的情况。这是因为父子进程抢占CPU,执行顺序不确定。如果父进程先结束,
$ 就会立即打印提示符。解决方法是在父进程的
bash 之前加上
wait(),确保子进程大概率先执行完。
sleep(1)
二、 绘制“进程家族谱” ——
ps 命令
ps
一个 进程又是谁创建的呢?追根溯源,Linux 系统中所有的进程构成了一棵巨大的进程树。树根就是大名鼎鼎的
bash 进程,它的 PID 永远是 1,是所有进程的“始祖”。
init
我们可以使用 命令来查看系统中的所有进程。
ps aux
: 显示所有终端的进程。
a: 以用户为中心显示信息。
u: 显示没有控制终端的进程。
x
【实验二:追根溯源,找到 】
init
获取当前 的 PID 在终端中输入
bash
echo $$
$ echo $$
37580
这个 就是我们上个实验中看到的父进程的“父亲”。
37580
使用 和
ps 查找它的信息
grep
ps aux | grep 37580
运行结果 (部分)
coder 37580 0.0 0.1 13284 7456 pts/0 Ss 14:30 0:00 /bin/bash
我们找到了这个 进程。
bash
使用 命令更直观地查看进程树
pstree
pstree -p 37580
运行结果
systemd(1)───login(37570)───bash(37580)
这下清晰了!我们的 是由
bash 进程创建的,而
login 最终可以追溯到 PID 为 1 的
login(在很多系统中是
systemd)。我们成功绘制出了一小段进程家族谱!
init
三、 深入本质:系统调用 vs. 库函数
、
getpid()、
fork()、
open()… 这些函数为什么有如此大的“魔力”,能够接触到进程PID、创建新进程、操作硬件?而像
read()、
strcpy()、
printf() 这些函数似乎只能在程序自己的小世界里打转?
malloc()
答案在于它们身份的不同:前者是系统调用(System Call),后者是库函数(Library Function)。
如何区分它们?—— “二卷硬件内核访,三卷纯在用户晃”
这是个简单又好记的口诀。在 Linux 中,我们可以用 手册来查询一个函数的“卷号”。
man
第 2 卷:System Calls。这些函数是通往操作系统内核的桥梁。第 3 卷:Library Functions。这些函数是 C 语言标准库提供的,运行在用户空间。
【实验三:用 手册给函数“验明正身”】
man
查询
getpid
man 2 getpid
结果 (看第一行)
GETPID(2) Linux Programmer's Manual GETPID(2)
卷号是 2,确认是系统调用。
查询
malloc
man 3 malloc
结果
MALLOC(3) Linux Programmer's Manual MALLOC(3)
卷号是 3,确认是库函数。
为什么要有这种区分?
系统调用:是用户程序请求内核服务的唯一合法途径。它需要满足以下任一条件:
访问内核数据结构:比如 需要复制进程控制块(PCB)。访问硬件资源:比如
fork() 需要操作磁盘控制器。 执行系统调用会伴随着从用户态到内核态的切换,开销较大。
read()
库函数:完全运行在用户态,不涉及内核。它们通常是为了方便编程(如 )或对系统调用进行封装(如
strcpy 最终可能会调用
printf 系统调用)。
write 是一个特例,它管理的是用户空间的堆内存,所以本身是库函数。
malloc
总结
今天,我们的“进程侦探”之旅收获颇丰:
我们学会了使用 和
getpid() 来进行进程的“亲子鉴定”。我们利用
getppid() 和
ps 命令,成功绘制出了进程的“家族谱”,理解了所有进程最终都源于
pstree (PID 1) 的树状结构。我们深入到了问题的核心,学会了区分系统调用和库函数,并理解了它们在权限和功能上的本质差异。
init
从一个简单的 函数出发,我们不仅理清了进程间的父子关系,更窥见了整个操作系统内核与用户程序交互的宏伟蓝图。这正是学习技术的乐趣所在——由点及面,洞见本质。
getppid()




















暂无评论内容