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

Linux加载一个可执行程序并启动的过程的更多相关文章

  1. Tomcat启动时自动加载一个类

    有时候在开发Web应用的时候,需要tomcat启动后自动加载一个用户的类,执行一些初始化方法,如从数据库中加载业务字典到内存中,因此需要在tomcat启动时就自动加载一个类,或运行一个类的方法. 可以 ...

  2. JavaWeb 服务启动时,在后台启动加载一个线程

    JavaWeb 服务启动时,在后台启动加载一个线程. 目前,我所掌握的一共有两种方法,第一种是监听(Listener),第二种是配置随项目启动而启动的Servlet. 下面对这两种方法做一简单的介绍, ...

  3. 如何在tomcat启动时自动加载一个类

    有时候在开发web应用的时候,需要tomcat启动后自动加载一个用户的类,执行一些初始化方法,如从数据库中加载业务字典到内存中,因此需要在tomcat启动时就自动加载一个类,或运行一个类的方法. 可以 ...

  4. tomcat启动时自动加载一个类 MyServletContextListener

    目的: 我们知道在tomcat启动后,需要页面请求进行驱动来执行操作接而响应.我们希望在tomcat启动的时候能够自动运行一个后台线程,以处理我们需要的一些操作.因此需要tomcat启动时就自动加载一 ...

  5. linux加载和卸载模块

    模块建立之后, 下一步是加载到内核. 如我们已指出的, insmod 为你完成这个工作. 这个 程序加载模块的代码段和数据段到内核, 接着, 执行一个类似 ld 的函数, 它连接模块中 任何未解决的符 ...

  6. jQuery加载一个html页面到指定的div里

    一.jQuery加载一个html页面到指定的div里 把a.html里面的某一部份的内容加载到b.html的一个div里.比如:加载a.html里面的<div id=“row"> ...

  7. 无法加载一个或多个请求的类型。有关更多信息,请检索 LoaderExceptions 属性。

    新建一个MVC4的项目,引用DAL后,将DAL的连接字符串考入: <connectionStrings>     <add name="brnmallEntities&qu ...

  8. “无法加载一个或多个请求的类型。有关更多信息,请检索 LoaderExceptions 属性 “之解决

    今天在学习插件系统设计的时候遇到一个问题:“System.Reflection.ReflectionTypeLoadException: 无法加载一个或多个请求的类型. 于是百度一下,很多内容都差不多 ...

  9. Android 编程下 WebView 加载一个网页如何得到网页的 Cookie 值

    http://www.cnblogs.com/sunzn/archive/2013/04/03/2998113.html mWebView.setWebViewClient(new MyWebView ...

随机推荐

  1. 一款基于jQuery Ajax的等待效果

    特别提示:本人博客部分有参考网络其他博客,但均是本人亲手编写过并验证通过.如发现博客有错误,请及时提出以免误导其他人,谢谢!欢迎转载,但记得标明文章出处:http://www.cnblogs.com/ ...

  2. QPS、TPS、PV、UV、GMV、IP、RPS

    摘自:https://www.citrons.cn/jishu/226.html 关于 QPS.TPS.PV.UV.GMV.IP.RPS 这些词语,看起来好像挺专业.但实际上,我认为是这是每个程序员必 ...

  3. kurento搭建以及运行kurento-hello-world

    搭建环境的系统是ubuntu 1.kurento服务器搭建 运行如下脚本即可完成安装 #!/bin/bash echo "deb http://ubuntu.kurento.org trus ...

  4. 一、基础篇--1.1Java基础-String、StringBuilder、StringBuffer

    String.StringBuilder.StringBuffer 主要区别在两点上: 速度效率上对比:StringBuilder>StringBuffer>String 线程安全上来说: ...

  5. python3 导入模块

    python3导入模块和python2  有些不同   需要指定相对目录 如,在Project下有一个nlp目录里面有一个ltp模块,则 from n1.ltp import Clawer

  6. 快速入门分布式消息队列之 RabbitMQ(2)

    目录 目录 前文列表 RabbitMQ 的特性 Message Acknowledgment 消息应答 Prefetch Count 预取数 RPC 远程过程调用 vhost 虚拟主机 插件系统 最后 ...

  7. Python学习之==>日志模块

    一.logging模块介绍 logging是Python中自带的标准模块,是Python中用来操作日志的模块. 1.控制台输出日志 import logging logging.basicConfig ...

  8. delphi xe2 64位嵌入汇编问题 https://bbs.csdn.net/topics/390333981

    Function xxx(xxx):xxx;assembler;asm  XOR RAX , RAX  ...end;这样的可以. 0 0 引用 ・ 举报 ・ 管理 5t4rk   回复于 2013- ...

  9. Java ——接口

    本节重点思维导图 定义: public interface Traffic { public static final int sits = 4; public abstract void run() ...

  10. is_displayed()检查元素是否可见

    返回的结果是bool类型,以百度首页为案例,来验证"©2019 Baidu 使用百度前必读意见反馈京ICP证030173号 "是否可见,见实现的代码: from selenium ...