Linux内核之进程和系统调用
一.fork和execl
我们先尝试编写创建两个程序,来理解Linux创建新进程的过程:
1 #include <sys/systypes.h> 2 #include <unistd.h> 3 4 int main() 5 { 6 if (fork() == 0) { 7 print("Child process!\n"); 8 } else { 9 print("Parent process!\n"); 10 } 11 return 0; 12 }
运行结果为:
Child process!
Parent process!
代码二
1 #include <sys/types.h> 2 #include <unistd.h> 3 #include <stdio.h> 4 5 int main() 6 { 7 execl("./hello_world", NULL, NULL); 8 printf("execl failed!"); 9 return 0; 10 }
运行结果
Hello World!
通过上面的对比,可以看错:fork实际上是复制了一个“自己”,在fork之后有两个自己在运行。子进程和父进程是依靠fork的返回值是否为0来决定执行流。返回值为0则是子进程。返回值不是0则是父进程。
execl则是将执行的内容完全替换掉。在execl之后,执行的完全是一个新的进程。就进程不复存在。
可以将fork看作是复制,而execl则是替换。
linux就是这样创建进程的:fork一个进程,在用execl加载新的执行程序,替换新建的进程的执行内容。比如ls:bash通过fork一个新的bash,再execl("ls")来创建一个ls进程。
二.系统调用的过程
想要详细定位fork和execl在内核中的实现需要了解linux系统调用的处理流程。我们以fork为例子。我们可以通过下面的代码调用fork:
#include <memory.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> int main() { pid_t pid; // pid = fork(); asm volatile( "mov $0x2, %%eax\n\t" // 将fork的系统调用号2存到eax寄存器 "int $0x80\n\t" // 产生int 0x80中断 "mov %%eax,%0\n\t" // 将结果存入pid中 : "=m" (pid) ); if (pid == 0) { printf("Child process\n"); } else if (pid > 0) { printf("Parent process\n"); } return 0; }
这样就能通过内核调用fork了。可以看到我们是通过int 0x80这条指令实现的。实际上,这是一个中断。我们通过128号中断调用内核的中断服务例程,调用fork的。
在系统调用的过程中,我们需要记住两张表。
一、中断向量表:在内核启动时初始化,然后当有中断发生时,内核就会根据中断向量号(如:0x80),查找对应的中断处理服务程序。
二、系统调用表:在编译内核时就已经卸载内核中,系统调用处理程序会根据系统调用号处理对应的系统调用程序。
例如,上图的getpid函数,然后进入getuid系统调用。getuid进入system_call系统中断服务例程,system_call再去调用sys_getuid16运行服务例程。
三. 查找对应的内核服务例程
通过上面的理解,我们就可以很快的使用source insight来查找fork和execl对应的内核服务例程了。
fork对应的是:kernel/fork.c中的sys_fork函数
execl对应的是:fs/exec.c中的sys_execl函数。
这些函数太过复杂,需要非常好的基础才能看懂。我目前基础不够,只能结合《深入理解linux内核》中的进程和文件系统两章做一点简单的理解。这里就无法再做分析了。只对理解的部分做出一点总结。