Linux加载一个可执行程序并启动的过程

原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

作者:严哲璟

以shell下执行ls命令为例介绍Linux通过fork()和execve()类函数的执行程序启动过程:

父进程为shell,命令为ls,目录为/bin/ls  

当输入ls时,shell进程通过fork()创建一个新的子进程,fork()进程复制代码,以及新建堆栈等之前已经说明,子进程有机会执行的时候,在ret_from_fork()开始,返回到子进程的用户堆栈中,执行其余的子进程的代码.

在这些子进程需要执行的代码中,有execve(/bin/ls,ls,NULL),ls是列出当前路径的目录的一个可执行文件,同理如./a.out等

为加载此可执行文件到内存中执行,关键的地方在于,execve返回之后,执行的代码变成了需要加载的可执行文件的代码,下面详细说明它是如何做到的.

首先 execve()函数是系统调用,陷入内核,调用do_execve_common()函数,此函数的作用是加载需要执行的可执行文件

  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;

 

该函数用到了一个类型为linux_binprm的结构体来保存要执行的文件相关的信息,该结构体在include/linux/binfmts.h文件中定义:
struct linux_binprm{
    char buf[BINPRM_BUF_SIZE]; //保存可执行文件的头128字节
    struct page *page[MAX_ARG_PAGES];
    struct mm_struct *mm;
    unsigned long p;    //当前内存页最高地址
    int sh_bang;
    struct file * file;     //要执行的文件
    int e_uid, e_gid;    //要执行的进程的有效用户ID和有效组ID
    kernel_cap_t cap_inheritable, cap_permitted, cap_effective;
    void *security;
    int argc, envc;     //命令行参数和环境变量数目
    char * filename;    //要执行的文件的名称
    char * interp;        //要执行的文件的真实名称,通常和filename相同
    unsigned interp_flags;
    unsigned interp_data;
    unsigned long loader, exec;
};
    在该函数的最后,又调用了fs/exec.c文件中定义的search_binary_handler函数来查询能够处理相应可执行文件格式的处理器,并调用相应的load_library方法以启动进程。这里,用到了一个在include/linux/binfmts.h文件中定义的linux_binfmt结构体来保存处理相应格式的可执行文件的函数指针如下:
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 *);
    // 将当前进程的上下文保存在一个名为core的文件中
    int (*core_dump)(long signr, struct pt_regs * regs, struct file * file);
    unsigned long min_coredump;
};

Linux内核允许用户通过调用在include/linux/binfmt.h文件中定义的register_binfmt和unregister_binfmt函数来添加和删除linux_binfmt结构体链表中的元素,以支持用户特定的可执行文件类型。

 

static int __init init_elf_binfmt(void)
{
	register_binfmt(&elf_format);
return 0;
}
加载的可执行文件进程开始:
start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)
199{
200	set_user_gs(regs, 0);
201	regs->fs		= 0;
202	regs->ds		= __USER_DS;
203	regs->es		= __USER_DS;
204	regs->ss		= __USER_DS;
205	regs->cs		= __USER_CS;
206	regs->ip		= new_ip;
207	regs->sp		= new_sp;
208	regs->flags		= X86_EFLAGS_IF;
209	/*
210	 * force it to the iret return path by making it look as if there was
211	 * some work pending.
212	 */
213	set_thread_flag(TIF_NOTIFY_RESUME);
214}

 
 

 

转载于:https://www.cnblogs.com/yzjustc/p/yzj.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值