fork和exec系统调用在内核中的执行过程
一、编程实现一个简单的shell程序
大体上,先fork()一个子进程, 然后在子进程里执行新程序,父进程则等待子进程执行结束。子进程的fork()返回0,父进程的fork()返回子进程的进程ID,以此可以区分两个进程。在子进程中通过调用execl来执行新程序,execl函数的签名为:
int execl(const char * path, const char * arg, ...);
为了调用"/bin/"中的程序,先定义一个路径字符串"/bin/",然后把新程序的名字拼接在后面,作为execl的第一个参数传入。后面是新程序的名称和它的一个参数,这里只支持给新程序最多一个参数。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <string.h> int main(int argc, char *argv[]) { pid_t pid; char path[255] = "/bin/"; if ( (pid=fork()) == 0) execl(strcat(path, argv[1]), argv[1], argv[2], NULL); else wait(NULL); return 0; }
将上述代码命名为myshell.c,然后gcc myshell.c -o myshell,然后执行./myshell echo hello,回显“hello”, 执行./myshell ls -l,显示当前目录下的详细文件信息。
二、分析fork和exec系统调用在内核中的执行过程
fork()系统调用其实封装的是do_fork()函数,du_fork()函数的是实现见这里1558行:http://lxr.free-electrons.com/source/kernel/fork.c,下面摘抄了一些重要的部分:
long do_fork(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *parent_tidptr, int __user *child_tidptr) {
struct task_struct *p; ...... p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace); if (!IS_ERR(p)) { ...... wake_up_new_task(p); /* forking complete and child started to run, tell ptracer */ ...... } else { nr = PTR_ERR(p); } return nr; }
可见它先把父进程复制了一份,返回struct task_struct结构的一个指针,然后唤醒子进程,基本上就完成了一个fork过程。可见fork主要的工作就是创建一个新进程,然后把父进程的栈等内容复制过去。
struct task_struct是进程控制块的结构,是进程最为重要的部分,里面定义有
volatile long state; 进程状态
unsigned int flags; 进程优先级
struct mm_struct *mm; 进程的内存使用信息
pid_t pid; 进程ID
struct task_struct *parent; 指向父进程
等等许多的信息。
对于exec函数来说,它主要执行的函数是do_execve_common(),其定义见这里1453行:http://lxr.free-electrons.com/source/fs/exec.c。
这个函数先打开要执行的文件:
file = open_exec(filename);
然后初始化并填好struct linux_binprm *bprm;这个结构体,这个结构题的定义见这里:http://lxr.free-electrons.com/source/include/linux/binfmts.h
struct linux_binprm { char buf[BINPRM_BUF_SIZE]; #ifdef CONFIG_MMU struct vm_area_struct *vma; unsigned long vma_pages; #else # define MAX_ARG_PAGES 32 struct page *page[MAX_ARG_PAGES]; #endif struct mm_struct *mm; unsigned long p; /* current top of mem */ unsigned int cred_prepared:1,/* true if creds already prepared (multiple * preps happen for interpreters) */ cap_effective:1;/* true if has elevated effective capabilities, * false if not; except for init which inherits * its parent's caps anyway */ #ifdef __alpha__ unsigned int taso:1; #endif unsigned int recursion_depth; struct file * file; struct cred *cred; /* new credentials */ int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */ unsigned int per_clear; /* bits to clear in current->personality */ int argc, envc; const char * filename; /* Name of binary as seen by procps */ const char * interp; /* Name of the binary really executed. Most of the time same as filename, but could be different for binfmt_{misc,script} */ unsigned interp_flags; unsigned interp_data; unsigned long loader, exec; char tcomm[TASK_COMM_LEN]; };
其第一个buf变量用来保存可执行文件的头128字节,page指针数组保存所用的页面,file指针则指向了要执行的文件。
填好bprm后,do_execve_common()接下来做了一件非常重要的事,就是调用search_binary_handler(),这个函数中会调用本文件全局变量formats的每一个load_binary()函数,来把elf文件读到内存中。这样,exec基本上就完成了。