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基本上就完成了。


posted on 2013-05-30 22:52  夜花烛  阅读(1474)  评论(0编辑  收藏  举报