原创作品转载请注明出处 + 《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}
												

最新文章

  1. wireshark lua脚本
  2. <<< struts 的一系列介绍
  3. servlet 之request
  4. iPhone5停留在语音的界面,提示按三次home键,无法继续下去
  5. DataTable 删除列 调整列顺序 修改列标题名称
  6. 利用C语言获得网页编码
  7. CPU介绍
  8. (转)u3d设计模式
  9. phalcon: (非官方)简单的多模块
  10. easyUI分页显示
  11. Windows Server 2008 R2安装IIS
  12. 原创C# 枚举 多状态 操作
  13. android3.2以上切屏禁止onCreate()
  14. JNI(2)
  15. Shell echo命令
  16. BeautifulSoup 用法
  17. mysql 语法积累
  18. [踩坑系列]URLEncode 中对 空格的编码有 “+”和“%20”两种
  19. C语言中数组变量和指针变量
  20. LeetCode 788 Rotated Digits 解题报告

热门文章

  1. PixelShuffle
  2. 什么是webpack以及为什么使用它
  3. 复选框checked 选中后不显示打钩
  4. IDEA 中常用快捷键的使用
  5. leetcode 287寻找重复数
  6. SPEL 表达式解析
  7. HTML5——新表单元素 表单属性 语义元素
  8. codeforces 1186C Vus the Cossack and Strings
  9. 实验报告5&第七周课程总结
  10. 2016青岛区域赛.Coding Contest(费用流 + 概率计算转换为加法计算)