原创作品转载请注明出处 + 《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. Java 有几种修饰符?分别用来修饰什么

    4种修饰符 访问权限   类   包  子类  其他包 public     ∨   ∨   ∨     ∨ protect    ∨   ∨   ∨     × default    ∨   ∨   ...

  2. IntelliJ IDEA2018破解教程

    破解方法:下载破解补丁→修改配置文件→输入激活码→激活成功 由于JetBrains封杀,大部分激活服务器已经不能使用,使用下面的比较麻烦的方法也可以进行破解,但是有效期是到2100年(emmmm,也算 ...

  3. maven之阿里云Maven镜像的使用

    Maven中央仓库在国外,速度比较慢,所以我们采用国内的镜像,速度回有质的提升. 配置下setting.xml <mirrors> <mirror> <id>ali ...

  4. 1、Shiro简介以及整体架构

    1.Shiro概念和作用: 利用shiro可以快速完成权限管理模块的开发 Spring的官网也是用Shiro做安全管理的... Shiro整体架构: 可能你感觉上面的图片很乱,但是你一定要先大体有个印 ...

  5. 九、SpringBoot集成Thymeleaf模板引擎

    Thymeleaf咋读!??? 呵呵,是不是一脸懵逼...哥用我的大学四级英文知识告诉你吧:[θaimlif]. 啥玩意?不会音标?...那你就这样叫它吧:“赛母李府”,大部分中国人是听不出破绽的.. ...

  6. Mac下安装lightgbm

    Mac下安装lightgbm 1.安装环境 系统 MacOS Mojave 版本10.14.2 Xcode 10.1 $ clang -v Apple LLVM version 10.0.0 (cla ...

  7. 查看dll中的函数(方法)

    https://jingyan.baidu.com/article/5553fa82b953b365a23934b7.html 查看dll中的函数(方法) 听语音 | 浏览:2004 | 更新:201 ...

  8. Java 项目管理工具 - Maven

    类似于 PHP 中的 Composer,NodeJS 中的 npm,Java 用 Maven 来管理依赖关系. 实际上,Maven 负责管理 Java 项目开发过程中的几乎所有的东西: 版本控制:Ma ...

  9. 使用 go protoc --go_out 输出的 *.pb.go文件时报 undefined: proto.ProtoPackageIsVersion3

    事情是这样的:我参考go的 grpc 实现 https://grpc.io/docs/quickstart/go/ Download the example The grpc code that wa ...

  10. .NetCore2.0项目之ABP+Vue(IView框架)单页应用之路,启动

    首先很久没有好好静下心来做点东西了,一直用忙碌做借口,实际还是懒,今天决定动一动. 第一步,下载自己的项目模板 首先vue项目国内的暂时还没有,要登录https://aspnetboilerplate ...