原創作品轉載請注明出處 + 《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}