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}

 
        

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM