linux內核情景分析之execve的實現

linux內核情景分析之execve的實現

來自專欄 深入理解Linux內核筆記(基於2.6內核)

用來描述用戶態的cpu寄存器在內核棧中保存情況.可以獲取用戶空間的信息

struct pt_regs { long ebx; //可執行文件路徑的指針(regs.ebx中 long ecx; //命令行參數的指針(regs.ecx中) long edx; //環境變數的指針(regs.edx中)。 long esi; long edi; long ebp; long eax; int xds; int xes; long orig_eax; long eip; int xcs; long eflags; long esp; int xss;};

sys_execve源碼解析,可以看出sys_execve的主要實現在於do_execve

asmlinkage int sys_execve(struct pt_regs regs){ int error; char * filename; filename = getname((char *) regs.ebx);//把字元串從用戶空間拷貝到系統空間 error = PTR_ERR(filename);//判斷是否出錯 if (IS_ERR(filename)) goto out; //文件名 參數 NULL 傳入副本 error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, &regs);//ecx為args,edx為NULL if (error == 0) current->ptrace &= ~PT_DTRACE; putname(filename);//將之前為文件名分配的空間釋放掉out: return error;}

分析do_execve之前首先來了解一個結構linux_bin_fmt.linux內核每種被註冊的可執行程序格式都用linux_bin_fmt來存儲,其中記錄了可執行程序的載入和執行函數,同時我們需要一種方法來保存可執行程序的信息, 比如可執行文件的路徑, 運行的參數和環境變數等信息,即linux_bin_prm結構

/** This structure is used to hold the arguments that are used when loading binaries.*/struct linux_binprm { char buf[BINPRM_BUF_SIZE]; // 保存可執行文件的頭128位元組#ifdef CONFIG_MMU struct vm_area_struct *vma; unsigned long vma_pages;#else# define MAX_ARG_PAGES 32 struct page *page[MAX_ARG_PAGES];#endif struct mm_struct *mm; unsigned long p; /* current top of mem , 當前內存頁最高地址*/ unsigned int cred_prepared:1,/* true if creds already prepared (multiple * preps happen for interpreters) */ cap_effective:1;/* true if has elevated effective capabilities, * false if not; except for init which inherits * its parents caps anyway */#ifdef __alpha__ unsigned int taso:1;#endif unsigned int recursion_depth; /* only for search_binary_handler() */ struct file * file; /* 要執行的文件 */ struct cred *cred; /* new credentials */ int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */ unsigned int per_clear; /* bits to clear in current->personality */ int argc, envc; /* 命令行參數和環境變數數目 */ const char * filename; /* Name of binary as seen by procps, 要執行的文件的名稱 */ const char * interp; /* Name of the binary really executed. Most of the time same as filename, but could be different for binfmt_{misc,script} 要執行的文件的真實名稱,通常和filename相同 */ unsigned interp_flags; unsigned interp_data; unsigned long loader, exec;};

接著來看do_execve的實現

int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs){ struct linux_binprm bprm;//存儲可執行文件信息 struct file *file; int retval; int i; file = open_exec(filename);//打開目標文件 retval = PTR_ERR(file); if (IS_ERR(file)) return retval; bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);//1024*32減去一個指針的大小,當前頁最高地址 memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0])); //page指針數組初始化為0 bprm.file = file; bprm.filename = filename; bprm.sh_bang = 0;//可執行文件屬性.0表示二進位 bprm.loader = 0; bprm.exec = 0;//參數的起始地址(從上往下方向) if ((bprm.argc = count(argv, bprm.p / sizeof(void *))) < 0) {//對指針數組argv[]中參數的個數進行計數,而bprm.p / sizeof(void *)表示允許的最大值,由於agrv[]是在用戶空間而不在系統空間 allow_write_access(file);//防止其他進程在讀入可執行文件期間通過內存映射改變它的內容 fput(file); return bprm.argc; } if ((bprm.envc = count(envp, bprm.p / sizeof(void *))) < 0) {//同上環境變數 allow_write_access(file); fput(file); return bprm.envc; } retval = prepare_binprm(&bprm);//見下面的代碼,為bprm結構做準備,讀取128個位元組到緩衝區 if (retval < 0) goto out; retval = copy_strings_kernel(1, &bprm.filename, &bprm);//由於bprm.filename已經在系統空間了,所以copy_strings_kernel從系統空間中拷貝 if (retval < 0) goto out; bprm.exec = bprm.p;//參數的起始地址 retval = copy_strings(bprm.envc, envp, &bprm);//從用戶空間拷貝,參考copy_strings_kernel if (retval < 0) goto out; retval = copy_strings(bprm.argc, argv, &bprm);//從用戶空間拷貝,參考copy_strings_kernel if (retval < 0) goto out; retval = search_binary_handler(&bprm,regs);//已經從可執行文件頭部讀入了128個位元組存放在bprm的緩衝區,而且運行所需的參數和環境變數也已經搜集在bprm中,現在就由formats隊列中的成員逐個來認領,誰要識別到它代表的可執行文件格式,運行的時候就交給它 if (retval >= 0) /* execve success */ return retval;out: /*出錯,返回inode和釋放argv的頁 Something went wrong, return the inode and free the argument pages*/ allow_write_access(bprm.file); if (bprm.file) fput(bprm.file);//釋放文件對象 for (i = 0 ; i < MAX_ARG_PAGES ; i++) { struct page * page = bprm.page[i]; if (page) __free_page(page); } return retval;}

do_execve->prepare_binprm實現。將可執行文件的頭部128位元組(elf)讀入到bprm->buf緩存中

int prepare_binprm(struct linux_binprm *bprm){ int mode; struct inode * inode = bprm->file->f_dentry->d_inode;//獲取打開的文件節點 mode = inode->i_mode;//類型 /* Huh? We had already checked for MAY_EXEC, WTF do we check this? */ if (!(mode & 0111)) /* with at least _one_ execute bit set最少可執行狀態 */ return -EACCES; if (bprm->file->f_op == NULL)//針對文件操作不允許為空 return -EACCES; bprm->e_uid = current->euid;//繼承uid bprm->e_gid = current->egid;//繼承gid if(!IS_NOSUID(inode)) {//是否是SUID /* Set-uid? */ if (mode & S_ISUID)//如果有SUID.那就設置 bprm->e_uid = inode->i_uid; /* Set-gid? */ /* * If setgid is set but no group execute bit then this * is a candidate for mandatory locking, not a setgid * executable. */ if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) bprm->e_gid = inode->i_gid; } /* We dont have VFS support for capabilities yet */ cap_clear(bprm->cap_inheritable); cap_clear(bprm->cap_permitted); cap_clear(bprm->cap_effective); /* To support inheritance of root-permissions and suid-root * executables under compatibility mode, we raise all three * capability sets for the file. * * If only the real uid is 0, we only raise the inheritable * and permitted sets of the executable file. */ if (!issecure(SECURE_NOROOT)) { if (bprm->e_uid == 0 || current->uid == 0) { cap_set_full(bprm->cap_inheritable); cap_set_full(bprm->cap_permitted); } if (bprm->e_uid == 0) cap_set_full(bprm->cap_effective); } memset(bprm->buf,0,BINPRM_BUF_SIZE);//從可執行文件中讀入開頭的128個位元組到linux_binprm結構bprm中的緩衝區 return kernel_read(bprm->file,0,bprm->buf,BINPRM_BUF_SIZE);}

linux內核對所支持的每種可執行的程序類型都有個struct linux_binfmt的數據結構,定義如下

struct linux_binfmt { struct list_head lh; struct module *module; int (*load_binary)(struct linux_binprm *);//通過讀存放在可執行文件中的信息為當前進程建立一個新的執行環境 int (*load_shlib)(struct file *);//用於動態的把一個共享庫捆綁到一個已經在運行的進程, 這是由uselib()系統調用激活的 int (*core_dump)(struct coredump_params *cprm);//在名為core的文件中, 存放當前進程的執行上下文. 這個文件通常是在進程接收到一個預設操作為」dump」的信號時被創建的, 其格式取決於被執行程序的可執行類型 unsigned long min_coredump; /* minimal dump size */ };

search_binary_handler,已經從可執行文件頭部讀入了128個位元組存放在bprm的緩衝區,而且運行所需的參數和環境變數也已經搜集在bprm中,現在就由formats隊列中的成員逐個來認領,誰要識別到它代表的可執行文件格式,運行的時候就交給它,代碼如下

int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs){ int try,retval=0; struct linux_binfmt *fmt; ......//一共2層循環,外出循環式為了安裝了模塊後再試一次 for (try=0; try<2; try++) { read_lock(&binfmt_lock);//所有的linux_binfmt對象都處於一個鏈表中, 第一個元素的地址存放在formats變數中 for (fmt = formats ; fmt ; fmt = fmt->next) { int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary; if (!fn)//是否是可執行文件 continue; if (!try_inc_mod_count(fmt->module))//是否是動態安裝庫 continue; read_unlock(&binfmt_lock); retval = fn(bprm, regs);//載入,如果誰都不認識,返回-ENOEXEC表示對不上號 if (retval >= 0) { put_binfmt(fmt); allow_write_access(bprm->file); if (bprm->file) fput(bprm->file); bprm->file = NULL; current->did_exec = 1; return retval; } read_lock(&binfmt_lock); put_binfmt(fmt); if (retval != -ENOEXEC)//只要不是對不上號,就退出循環 break; if (!bprm->file) { read_unlock(&binfmt_lock); return retval; } } read_unlock(&binfmt_lock); if (retval != -ENOEXEC) { break;#ifdef CONFIG_KMOD }else{#define printable(c) (((c)== ) || ((c)==
) || (0x20<=(c) && (c)<=0x7e)) char modname[20]; if (printable(bprm->buf[0]) && printable(bprm->buf[1]) && printable(bprm->buf[2]) && printable(bprm->buf[3])) break; /* -ENOEXEC */ sprintf(modname, "binfmt-%04x", *(unsigned short *)(&bprm->buf[2])); request_module(modname);//試著將相應的模塊裝入,一共進行兩次循環,正是為了在安裝了模塊以後再來試一次#endif } } return retval;}

我們假設/bin/echo是a.out格式。所以fn(bprm, regs),執行的代碼是load_aout_binary。

static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs){ struct exec ex; unsigned long error; unsigned long fd_offset; unsigned long rlim; int retval; ex = *((struct exec *) bprm->buf); //128位元組可執行文件頭部 if ((N_MAGIC(ex) != ZMAGIC && N_MAGIC(ex) != OMAGIC && N_MAGIC(ex) != QMAGIC && N_MAGIC(ex) != NMAGIC) || N_TRSIZE(ex) || N_DRSIZE(ex) || bprm->file->f_dentry->d_inode->i_size < ex.a_text+ex.a_data+N_SYMSIZE(ex)+N_TXTOFF(ex)) { return -ENOEXEC;//匹配不會返回-ENOEXEC } fd_offset = N_TXTOFF(ex);//根據代碼的特性取得代碼段在目標文件中的起始位置 /* Check initial limits. This avoids letting people circumvent * size limits imposed on them by creating programs with large * arrays in the data or bss. */ rlim = current->rlim[RLIMIT_DATA].rlim_cur;//獲取限制條件 if (rlim >= RLIM_INFINITY) rlim = ~0; if (ex.a_data + ex.a_bss > rlim)//代碼段和bss段的總和不能超過限制 return -ENOMEM; /* Flush all traces of the currently running executable */ retval = flush_old_exec(bprm);//到了"與過去告別"的時候了,這種"告別過去"意味著放棄從父進程"繼承"下來的全部用戶空間,不管是通過複製還是通過指針共享繼承下來的 if (retval) return retval; /* OK, This is the point of no return */#if !defined(__sparc__) set_personality(PER_LINUX);#else set_personality(PER_SUNOS);#if !defined(__sparc_v9__) memcpy(current->thread.core_exec, &ex, sizeof(struct exec));#endif#endif current->mm->end_code = ex.a_text + (current->mm->start_code = N_TXTADDR(ex));//代碼段起始和結束地址,詳細請看緊鄰本代碼段的相關代碼 current->mm->end_data = ex.a_data + (current->mm->start_data = N_DATADDR(ex));//數據段起始和結束地址,數據段起始地址就是代碼段結束地址 current->mm->brk = ex.a_bss + (current->mm->start_brk = N_BSSADDR(ex));//bss段起始和結束地址,bss起始地址就是數據段結束地址 current->mm->rss = 0; current->mm->mmap = NULL; compute_creds(bprm);//確定是否具有其許可權 current->flags &= ~PF_FORKNOEXEC;#ifdef __sparc__ if (N_MAGIC(ex) == NMAGIC) {//其他格式 loff_t pos = fd_offset;//elf代碼段偏移地址 /* Fuck me plenty... */ error = do_brk(N_TXTADDR(ex), ex.a_text); bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex), ex.a_text, &pos); error = do_brk(N_DATADDR(ex), ex.a_data); bprm->file->f_op->read(bprm->file, (char *) N_DATADDR(ex), ex.a_data, &pos); goto beyond_if; }#endif if (N_MAGIC(ex) == OMAGIC) {//如果魔術是OMAGIC unsigned long text_addr, map_size; loff_t pos; text_addr = N_TXTADDR(ex);//代碼段起始地址#if defined(__alpha__) || defined(__sparc__) pos = fd_offset;//根據代碼的特性取得代碼段在目標文件中的起始位置 map_size = ex.a_text+ex.a_data + PAGE_SIZE - 1;//代碼段和數據段的大小#else pos = 32; map_size = ex.a_text+ex.a_data;#endif error = do_brk(text_addr & PAGE_MASK, map_size);//為正文段和數據段合在一起分配空間 if (error != (text_addr & PAGE_MASK)) {//出錯 send_sig(SIGKILL, current, 0);//殺死當前進程 return error; } error = bprm->file->f_op->read(bprm->file, (char *)text_addr, ex.a_text+ex.a_data, &pos);//然後就把這兩部分從文件中讀進來,這樣就有了vm_struct結構,對應的頁目錄表項,頁表項,頁面,而且頁面已經放入了文件中的代碼段和數據段 if (error < 0) { send_sig(SIGKILL, current, 0); return error; } flush_icache_range(text_addr, text_addr+ex.a_text+ex.a_data); } else {//如果魔術不是OMAGIC static unsigned long error_time, error_time2; if ((ex.a_text & 0xfff || ex.a_data & 0xfff) && (N_MAGIC(ex) != NMAGIC) && (jiffies-error_time2) > 5*HZ) { printk(KERN_NOTICE "executable not page aligned
"); error_time2 = jiffies; } if ((fd_offset & ~PAGE_MASK) != 0 && (jiffies-error_time) > 5*HZ) { printk(KERN_WARNING "fd_offset is not page aligned. Please convert program: %s
", bprm->file->f_dentry->d_name.name); error_time = jiffies; } if (!bprm->file->f_op->mmap||((fd_offset & ~PAGE_MASK) != 0)) {//如果沒有mmap函數或者代碼段及數據段的長度不與頁面大小對齊 loff_t pos = fd_offset; do_brk(N_TXTADDR(ex), ex.a_text+ex.a_data);//和上面的方式一樣,正文段和數據段合在一起分配空間 bprm->file->f_op->read(bprm->file,(char *)N_TXTADDR(ex), ex.a_text+ex.a_data, &pos);//然後就把這兩部分從文件中讀進來,這樣就有了vm_struct結構,對應的頁目錄表項,頁表項,頁面,而且頁面已經放入了文件中的代碼段和數據段 flush_icache_range((unsigned long) N_TXTADDR(ex), (unsigned long) N_TXTADDR(ex) + ex.a_text+ex.a_data); goto beyond_if; } down(current->mm->mmap_sem); error = do_mmap(bprm->file, N_TXTADDR(ex), ex.a_text,//如果有mmap,將文件的代碼段映射到進程的用戶空間 PROT_READ | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE, fd_offset); up(current->mm->mmap_sem); if (error != N_TXTADDR(ex)) { send_sig(SIGKILL, current, 0); return error; } down(current->mm->mmap_sem); error = do_mmap(bprm->file, N_DATADDR(ex), ex.a_data,///如果有mmap,將文件的數據段映射到進程的用戶空間 PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE, fd_offset + ex.a_text);//數據段在文件中的偏移是fd_offset + ex.a_text up(current->mm->mmap_sem); if (error != N_DATADDR(ex)) { send_sig(SIGKILL, current, 0); return error; } }beyond_if: set_binfmt(&aout_format);//current->binfmt = aout_format; set_brk(current->mm->start_brk, current->mm->brk);//為可執行文件的bss段分配空間並建立起頁面映射 retval = setup_arg_pages(bprm); //在用戶空間的堆棧區頂部為進程建立起一個虛擬區間,並將執行參數以及環境變數所佔的物理頁面與此虛擬區間建立起映射 if (retval < 0) { /* Someone check-me: is this error path enough? */ send_sig(SIGKILL, current, 0); return retval; } current->mm->start_stack = (unsigned long) create_aout_tables((char *) bprm->p, bprm);//把一些變數放入用戶堆棧中,返回的是STACK_TOP減去圖中參數所佔空間,也就是堆棧開始的虛擬地址#ifdef __alpha__ regs->gp = ex.a_gpvalue;#endif //起始地址 ,棧指針 start_thread(regs, ex.a_entry, current->mm->start_stack);//設置系統堆棧中的eip,esp if (current->ptrace & PT_PTRACED) send_sig(SIGTRAP, current, 0); return 0;}

load_aout_binary->flush_old_exec實現

複製信號處理函數指針數組,放棄用戶空間或者不跟父進程共享內存空間,如果此進程實際為一個線程組的線程,那就從線程組中脫離,改變信號處理模式為默認處理,關閉file文件對象

int flush_old_exec(struct linux_binprm * bprm){ char * name; int i, ch, retval; struct signal_struct * oldsig; /* * Make sure we have a private signal table */ oldsig = current->sig;//獲取當前進程信號處理函數 retval = make_private_signals();//如果子進程通過指針來共享父進程的信號處理表,就要把它複製過來 if (retval) goto flush_failed; /* * Release all of the old mmap stuff */ retval = exec_mmap();//從父進程繼承下來的用戶空間就是在這裡放棄的 if (retval) goto mmap_failed; /* This is the point of no return */ release_old_signals(oldsig);//如果子進程通過指針來共享父進程的信號處理表,那麼就要減少oldsig->count的計數 current->sas_ss_sp = current->sas_ss_size = 0; if (current->euid == current->uid && current->egid == current->gid) current->dumpable = 1; name = bprm->filename; for (i=0; (ch = *(name++)) != ;) { if (ch == /) i = 0; else if (i < 15) current->comm[i++] = ch; } current->comm[i] = ;//comm[],用於保存進程所執行的程序名,所以還要把bprm->filename的目標程序路徑名的最後一段抄過去 flush_thread();//不用關心 de_thread(current);//如果當前進程是一個線程,從線程組中脫離出來 if (bprm->e_uid != current->euid || bprm->e_gid != current->egid || permission(bprm->file->f_dentry->d_inode,MAY_READ)) current->dumpable = 0; /* An exec changes our domain. We are no longer part of the thread group */ current->self_exec_id++; flush_signal_handlers(current);//原來指向用戶空間子程序的,現在設為SIG_DEL,表示採取預設的響應方式 flush_old_files(current->files);//對原有已打開文件的處理 return 0;mmap_failed:flush_failed: spin_lock_irq(current->sigmask_lock); if (current->sig != oldsig) kfree(current->sig); current->sig = oldsig; spin_unlock_irq(current->sigmask_lock); return retval;}

flush_old_exec中的make_private_signal

static inline int make_private_signals(void){ struct signal_struct * newsig; if (atomic_read(current->sig->count) <= 1)//如果只是把父進程的信號處理表指針複製過來,而通過這指針來共享父進程的信號處理表,那麼current->sig->count大於1 return 0; newsig = kmem_cache_alloc(sigact_cachep, GFP_KERNEL);//自立門戶,和copy_sighand()基本相同 if (newsig == NULL) return -ENOMEM; spin_lock_init(&newsig->siglock); atomic_set(&newsig->count, 1); memcpy(newsig->action, current->sig->action, sizeof(newsig->action));//拷貝 spin_lock_irq(current->sigmask_lock); current->sig = newsig;//獲得新的 spin_unlock_irq(current->sigmask_lock); return 0;}

flush_old_exec的exec_mmap函數

static int exec_mmap(void){ struct mm_struct * mm, * old_mm; old_mm = current->mm; if (old_mm && atomic_read(&old_mm->mm_users) == 1) {//如果共享計數為1時,表明對此空間的使用是獨佔的,也就是說這是從父進程複製過來的 flush_cache_mm(old_mm);//釋放緩存 mm_release();//針對vfork.父進程減少信號量休眠,以後讓子進程up將其喚醒 exit_mmap(old_mm);//釋放mm_struct數據結構以下的所有vm_area_struct數據結構(但是不包括mm_struct結構本身),並將頁面表項設置為0 flush_tlb_mm(old_mm);//釋放tlb的緩存 return 0; } mm = mm_alloc();//如果只是將指向mm_struct數據結構的指針複製給子進程,讓子進程通過這個指針來共享父進程的用戶空間,那就可以跳過釋放用戶空間這一步,直接就為子進程分配新的用戶空間 if (mm) { struct mm_struct *active_mm = current->active_mm;// if (init_new_context(current, mm)) {//空語句 mmdrop(mm); return -ENOMEM; } /* Add it to the list of mms */ spin_lock(&mmlist_lock); list_add(&mm->mmlist, &init_mm.mmlist); spin_unlock(&mmlist_lock); task_lock(current); current->mm = mm;//新分配空戶空間初始化 current->active_mm = mm;//新分配的空戶空間 task_unlock(current); activate_mm(active_mm, mm);//切換到新的的用戶空間 mm_release();//將父進程喚醒 if (old_mm) { if (active_mm != old_mm) BUG();//如果不一致,說明線程成為了進程 mmput(old_mm);//減少mm->mm_users計數,因為已經不共享了 return 0; } mmdrop(active_mm);//如果已經沒有進程使用這空間.將舊的空間給釋放掉 return 0; } return -ENOMEM;}

根據close_on_exec點陣圖,裡面存儲著表示哪些文件在執行一個新目標程序時應予關閉的信息。根據這個點陣圖的指示將這些文件關閉,並且將此點陣圖清成全0。

static inline void flush_old_files(struct files_struct * files){ long j = -1; write_lock(&files->file_lock); for (;;) { unsigned long set, i; j++; i = j * __NFDBITS; if (i >= files->max_fds || i >= files->max_fdset) break; set = files->close_on_exec->fds_bits[j]; if (!set) continue; files->close_on_exec->fds_bits[j] = 0; write_unlock(&files->file_lock); for ( ; set ; i++,set >>= 1) { if (set & 1) { sys_close(i); } } write_lock(&files->file_lock); } write_unlock(&files->file_lock);}

exec_mmap->mmput

void mmput(struct mm_struct *mm){ if (atomic_dec_and_lock(&mm->mm_users, &mmlist_lock)) {//減去1,是否為0 list_del(&mm->mmlist); spin_unlock(&mmlist_lock); exit_mmap(mm);//將頁表設置為0 mmdrop(mm);//將頁表項與頁表,本身mm_struct全部釋放 }}

load_aout_binary->setup_arg_pages

int setup_arg_pages(struct linux_binprm *bprm){ unsigned long stack_base; struct vm_area_struct *mpnt; int i; stack_base = STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE;//STACK_TOP為3GB bprm->p += stack_base;//bprm->p原來是MAX_ARG_PAGES*PAGE_SIZE-參數的數量,現在加上STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE,最後是STACK_TOP - 參數的數量 if (bprm->loader) bprm->loader += stack_base; bprm->exec += stack_base; mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//從slab中獲取一頁 if (!mpnt) return -ENOMEM; down(¤t->mm->mmap_sem); { mpnt->vm_mm = current->mm;//內存描述符 mpnt->vm_start = PAGE_MASK & (unsigned long) bprm->p;//堆棧段開始地址 mpnt->vm_end = STACK_TOP;//堆棧段結束地址 mpnt->vm_page_prot = PAGE_COPY; mpnt->vm_flags = VM_STACK_FLAGS; mpnt->vm_ops = NULL; mpnt->vm_pgoff = 0; mpnt->vm_file = NULL; mpnt->vm_private_data = (void *) 0; insert_vm_struct(current->mm, mpnt);//插入 current->mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT; } for (i = 0 ; i < MAX_ARG_PAGES ; i++) { struct page *page = bprm->page[i];//從page[0]開始 if (page) { bprm->page[i] = NULL; current->mm->rss++; put_dirty_page(current,page,stack_base);//建立從堆棧虛擬空間到頁面之前的映射 } stack_base += PAGE_SIZE;//加一個頁面的長度,4096個位元組 } up(&current->mm->mmap_sem); return 0;}

load_aout_binary->create_aout_tables

static unsigned long * create_aout_tables(char * p, struct linux_binprm * bprm){ char **argv, **envp; unsigned long * sp; int argc = bprm->argc;//程序參數個數 int envc = bprm->envc;//環境字元串個數 //sp指向當前堆棧頁面的棧頂指針 sp = (unsigned long *) ((-(unsigned long)sizeof(char *)) & (unsigned long) p);#ifdef __sparc__ /* This imposes the proper stack alignment for a new process. */ sp = (unsigned long *) (((unsigned long) sp) & ~7); if ((envc+argc+3)&1) --sp;#endif#ifdef __alpha__/* whee.. test-programs are so much fun. */ put_user(0, --sp); put_user(0, --sp); if (bprm->loader) { put_user(0, --sp); put_user(0x3eb, --sp); put_user(bprm->loader, --sp); put_user(0x3ea, --sp); } put_user(bprm->exec, --sp); put_user(0x3e9, --sp);#endif//sp減去envc+1作為環境字元串指針數組的起始地址,最後一個數組元素為0 sp -= envc+1; envp = (char **) sp;//指向環境字元串數組的起始地址 sp -= argc+1;//同上 argv = (char **) sp;#if defined(__i386__) || defined(__mc68000__) || defined(__arm__)//棧頂sp繼續向低地址放擴展,存放envp,argv,argc put_user((unsigned long) envp,--sp); put_user((unsigned long) argv,--sp);#endif put_user(argc,--sp); current->mm->arg_start = (unsigned long) p;//循環對堆棧中的環境字元串指針數組初始化 while (argc-->0) { char c; put_user(p,argv++); do { get_user(c,p++); } while (c); } put_user(NULL,argv); current->mm->arg_end = current->mm->env_start = (unsigned long) p;//env_start和arg_end while (envc-->0) { char c; put_user(p,envp++); do { get_user(c,p++); } while (c); } put_user(NULL,envp); current->mm->env_end = (unsigned long) p;//env_end return sp;//返回的是STACK_TOP減去圖中參數所佔空間}

1.使用execve首先將新的可執行文件的絕對路徑用戶空間拷貝到內核中

2.在得到可執行文件路徑後,就找到可執行文件打開,由於操作系統已經為可執行文件設置了一個數據結構,就讀取文件的前128位元組用來初始化這個數據結構,保存一個可執行文件必要的信息。

3.可執行文件不是直接分析前128自己然後自己運行的,需要有代理人來代理。在系統內核中有一個formats隊列,循環遍歷這個隊列,看看現在被初始化的這個數據結構是哪個代理人可以代理的。如果沒有就繼續查看數據結構中的信息。按照系統配置了是否可以動態載入模塊,載入一次模塊,再循環遍歷看是否有代理人前來認領。

4.找到正確的代理人後,代理人首先要做的就是放棄以前從父進程繼承來的資源(雖然代理程序各不相同,但這是一個共性的操作)。主要是對信號處理表,用戶空間和文件3大資源的處理。

a.信號處理表:將父進程的信號處理表複製過來(可能已經複製了,也可能沒有複製,通過檢查信號處理表的共享參數就可以知道)。信號處理分3種情況:一對該信號不理睬,二對信號採取默認動作,三對信號採取指定動作。前面2種可以直接複製,最後一種,所謂的默認動作就是用戶指定了程序處理,這段程序的代碼必然是父進程自己擁有的,他的位置就存在用戶空間中,下面我們會放棄繼承到的父進程用戶空間,對第3種情況的處理就是將其改成默認處理。所以,在新建的子進程中,對信號如果子進程沒有採取指定處理,那麼一律都會是默認處理,當然如果父進程對某個信號採取了不理睬,子進程也會不理睬,除非子進程後來又做了修改。

b.用戶空間,放棄原來的用戶空間(子進程可能有自己的頁面,或者就是通過指針共享了父進程的頁面)這些一律放棄,將進程式控制制塊task_struct中對用戶空間的描述的數據結構mm_struct的下屬結構vma全部置0.簡而言之就是現在子進程的用戶空間是個空架子,一個頁面也沒有,父進程空間被放棄。

c.進程式控制制塊task_struct中有file的指針記錄了進程打開的文件信息,子進程對繼承到的文件採取關閉應當關閉的信息。file的數據結構中有點陣圖記錄了應當關閉的文件,子進程放棄這些文件。一般來說,執行的效果是除了標準輸入文件,標準輸出文件,標準錯誤輸出文件。其它的文件都不會被子進程繼承。(標準輸入一般就是鍵盤,標準輸出就是顯示器。因此如果子進程有列印語句的話,那麼他的列印出來的字元會列印到父進程列印的地方,前面寫文章有點錯誤,我已經改掉了)。

5.至此我們已經做了的實際動作就是信號處理表,用戶空間和文件。但用戶空間是個空架子,真正的程序代碼沒載入,數據段也沒載入,堆棧沒有開闢,執行參數和環境變數也沒有被印射。但可以知道,每個可執行文件的載入是不同的,比如linux下shell文件和a.out文件2個有很明顯的不同,你可以對他們採用同樣的載入辦法嗎。下面就是各個代理人自己開始為自己的代理方申請空間,準備用戶內存。最後調用 start_thread 開始啟動進程。

這裡是大致的流程,很多細節沒有提到,比如在用戶空間釋放過程中,我們可以看到很多問題。例如原來的是vfork創建的是線程,你放了空間,為新進程申請了空間,雖然是個空架子,但他已經不是線程了,你要從父進程的線程組中拿掉他。如果是內核線程啟動了新的進程,內核線程是沒有自己的用戶空間的。這是原來用戶空間的共享計數會為0,該如何處理,這些問題在這裡都沒提到。如果有興趣,大家可以自己看linux源代碼

(總結轉自linux下execve實現的過程 - memorymyann - ITeye博客)


推薦閱讀:

龔神給微軟 Linux 子系統寫的支持 DirectX 9、11的代碼到底屬不屬於「驅動」?
基於什麼樣的理由或特徵可以判別某個系統是 Android 的修改版本而不是另一個基於 Linux 開發的系統?
Linux 伺服器安全簡明指南
Linux學習(1)—一些下載網址以及命令
新手如何入坑linux

TAG:Linux | 操作系統 |