内核源码阅读(七)新程序的启动与进程的退出

1.新程序的启动

通过用新代码替换现存程序,即可启动新程序。在Linux下这是通过execve系统调用实现的。
该系统调用的入口点是sys_execve函数,然后委托给do_execve函数。其原型如下:

kernel/exec.c
int do_execve(char * filename,
char user * user *argv, char user * user *envp, struct pt_regs * regs)
    do_execve的代码流程图:

do_execve的代码流程图

do_execve()的实现如下所示:
int do_execve(char * filename, char __user *__user *argv,
        char __user *__user *envp,     struct pt_regs * regs)
{
    struct linux_binprm *bprm;  /* 保存要执行的文件相关的数据 */
    struct file *file;
    int retval;
    int i;
    retval = -ENOMEM;
    bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);
    if (!bprm)
        goto out_ret;
    file = open_exec(filename);     /* 打开要执行的文件,并检查其有效性 */
    retval = PTR_ERR(file);
    if (IS_ERR(file))
        goto out_kfree;
    /* 在多处理器系统中才执行,用以分配负载最低的CPU来执行新程序
    该函数在include/linux/sched.h文件中被定义如下:
    #ifdef CONFIG_SMP
   extern void sched_exec(void);
  #else
   #define sched_exec() {}
  #endif
   sched_exec();  */
    /* 填充linux_binprm结构 */
    bprm->p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);
    bprm->file = file;
    bprm->filename = filename;
    bprm->interp = filename;
    bprm->mm = mm_alloc();
    retval = -ENOMEM;
    if (!bprm->mm)
        goto out_file;
    /* 检查当前进程是否在使用LDT,如果是则给新进程分配一个LDT */
    retval = init_new_context(current, bprm->mm);
    if (retval  0)
        goto out_mm;
    /* 继续填充linux_binprm结构 */
    bprm->argc = count(argv, bprm->p / sizeof(void *));
    if ((retval = bprm->argc)  0)
        goto out_mm;
    bprm->envc = count(envp, bprm->p / sizeof(void *));
    if ((retval = bprm->envc)  0)
        goto out_mm;
    retval = security_bprm_alloc(bprm);
    if (retval)
        goto out;
    /* 检查文件是否可以被执行,填充linux_binprm结构中的e_uid和e_gid项
   使用可执行文件的前128个字节来填充linux_binprm结构中的buf项 */
    retval = prepare_binprm(bprm);
    if (retval  0)
        goto out;
    /* 将文件名、环境变量和命令行参数拷贝到新分配的页面中 */
    retval = copy_strings_kernel(1, &bprm->filename, bprm);
    if (retval  0)
        goto out;
    bprm->exec = bprm->p;
    retval = copy_strings(bprm->envc, envp, bprm);
    if (retval  0)
        goto out;
    retval = copy_strings(bprm->argc, argv, bprm);
    if (retval  0)
        goto out;
    //查询能够处理该可执行文件格式的处理函数,并调用相应的load_library方法进行处理
    retval = search_binary_handler(bprm,regs);
    if (retval >= 0) {
        free_arg_pages(bprm);
        //执行成功
        security_bprm_free(bprm);
        acct_update_integrals(current);
        kfree(bprm);
        return retval;
    }
out:
    //发生错误,返回inode,并释放资源
    for (i = 0 ; i  MAX_ARG_PAGES ; i++) {
        struct page * page = bprm->page;
        if (page)
            __free_page(page);
    }
    if (bprm->security)
        security_bprm_free(bprm);
out_mm:
    if (bprm->mm)
        mmdrop(bprm->mm);
out_file:
    if (bprm->file) {
        allow_write_access(bprm->file);
        fput(bprm->file);
    }
out_kfree:
    kfree(bprm);
out_ret:
    return retval;
}
sys_execve的实现如下所示:
asmlinkage int sys_execve(struct pt_regs regs)
{
    int  error;
    char * filename;
    filename = getname((char __user *) regs.ebx);  /*将可执行文件的名称装入到一个新分配的页面中 */
    error = PTR_ERR(filename);
    if (IS_ERR(filename))
        goto out;
    //执行可执行文件
    error = do_execve(filename,
            (char __user * __user *) regs.ecx,
            (char __user * __user *) regs.edx,
            &regs);
    if (error == 0) {
        task_lock(current);
        current->ptrace &= ~PT_DTRACE;
        task_unlock(current);

        set_thread_flag(TIF_IRET);
    }
    putname(filename);
out:
    return error;
}                            
上述系统调用do_execve的参数在include/asm-i386/ptrace.h中被定义。
struct pt_regs {
    long ebx;
    long ecx;
    long edx;
    long esi;
    long edi;
    long ebp;
    long eax;
    int xds;
    int xes;
    long orig_eax;
    long eip;
    int xcs;
    long eflags;
    long esp;
    int xss;
};

Linux支持可执行文件的不同格式,标准格式是ELF,但是不同体系架构会使用许多不同的二进制格式,因此二进制格式程序不一定能够在多个架构上运行,二进制格式其实只是表示了可执行文件和内存中程序数据段、代码段等组织情况。
search_binary_handler用于在do_execve结束时查找适当二进制格式,用于所要执行的特定文件,其通常根据文件起始的魔数来识别,二进制格式处理程序则将新程序加载到旧的地址空间中。

二进制格式处理程序执行的操作:

  1. 释放原进程使用的所有资源;
  2. 将应用程序映射到虚拟地址空间。并对下列段进行处理:
    a.text包含程序的可执行代码段,start_code和end_code指定该段地址空间中驻留区域;
    b.预先初始化的数据位于start_data和end_data,并映射成可执行的对应段;
    c.堆用于动态内存分配,边界为start_brk和brk;
    d栈由start_stack定义位置;
    e.程序参数和环境变量也要映射如虚拟地址空间。

  3. 列表内容设置进程的指令指针和寄存器。
    Linux下程序在内存中的段分布如下图
    **这里写图片描述**

二进制格式的解释
在Linux内核中任何二进制格式都可用下面的结构体实例表示。

<binfmts.h>
struct linux_binfmt {
struct linux_binfmt * next; struct module *module;
int (*load_binary)(struct linux_binprm *, struct pt_regs * regs); int (*load_shlib)(struct file *);
int (*core_dump)(long signr, struct pt_regs * regs, struct file * file); unsigned long min_coredump;    /* minimal dump size */
};

其中
1)load_binary用于加载普通程序;
2)oad_shlib:用于加载共享库
3)core_dump:用于在程序错误情况下输出内存转储,随后可进行分析,以解决问题。
每种二进制格式必须使用register_binfmt向内核注册,才能加载
每种二进制格式必须使用register_binfmt向内核注册,才能加载。
1.//linux二进制程序的参数结构体  
2.struct linux_binprm{  
3.  char buf[128];                        //128位的缓存  
4.  unsigned long page[MAX_ARG_PAGES];    //页数组  
5.  unsigned long p;                      //               
6.  int sh_bang;                          //sh   
7.  struct inode * inode;                 //程序点机构体指针  
8.  int e_uid, e_gid;                     //程序的uid,gid  
9.  int argc, envc;                       //程序的输入参数数和环境参数数  
10.  char * filename;     /* Name of binary */  
11.}

2.进程的退出

进程必须使用exit()系统调用退出使得内核能够释放系统资源,该调用的入口点是sys_exit(),sys_exit()需要一个错误码以便退出进程,最后其将工作委托给do_exit()。
该函数的实现主要是将各个引用计数器减1,若减为0无进程在调用,则上交其内存给内存管理模块。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值