程式執行的時候需要命令列引數,linux中更是這樣,隨便在shell輸入/bin/xx --help後列舉出來的引數讓你頭暈眼花,可是這些引數是怎麼進入程式的呢,我們知道程式執行的時候一般從main開始,而mian有兩個引數,乙個是 argc代表引數的個數,乙個是argv代表具體字串型別的引數,這是我們所看到的,我們都知道函式的引數都在堆疊中,在呼叫函式前,主調函式應該將引數壓入堆疊後再呼叫被調函式,那麼是誰呼叫的main函式呢?又是誰將main的引數壓入堆疊的呢?
關於第乙個問題,是誰呼叫的main函式,我就不多說了,因為網上已經有了一篇叫做《before main》的文章了,寫得非常好,可以搜尋一下,讀了此文你會明白實際上使用者程序的開始函式並不是main,在main之前還有很多任務作要做,但是如果說 是xx呼叫了main,那麼就是xx壓入了引數,我們很多人喜歡糾著乙個問題一直到底,那我們就較較真兒,又是誰將引數給了xx呢?我們開始乙個程式的時 候要呼叫exec系列函式,比如execve,我們看看execve的宣告:
int execve(const char *filename, char *const argv,char *const envp);
我 們看一下這第二個和第三個引數實際上就是main的引數(main的第乙個引數argc是由這些引數算出來的),而呼叫execve的時候還是原來的進 程,新的程序還只是乙個filename,具體能否執行還有待商榷呢,新程序根本沒有對映進使用者空間,這時這些引數是怎麼傳遞給新的程序的呢?我們於是就來正式解答第二個問題:又是誰將main的引數壓入堆疊的呢?
研究linux有個好的不得了的資源就是核心,當你遇到任何棘手的問題都可以從核心得到解答,當然今天我們的問題並不算棘手!我們還是看看sys_ececve是怎麼做的:
asmlinkage int sys_execve(struct pt_regs regs)
int error;
char * filename;
filename = getname((char __user *) regs.ebx);
error = ptr_err(filename);
if (is_err(filename))
goto out;
error = do_execve(filename,
(char __user * __user *) regs.ecx,
(char __user * __user *) regs.edx,
®s);
...//我們今天的問題到此為止,以下省略
繼續do_execve
int do_execve(char * filename,
char __user *__user *argv,
char __user *__user *envp,
struct pt_regs * regs)
struct linux_binprm *bprm;
bprm = kzalloc(sizeof(*bprm), gfp_kernel);
bprm->file = file;
bprm->filename = filename;
bprm->interp = filename;
bprm->argc = count(argv, max_arg_strings);
if ((retval = bprm->argc)
goto out_mm;
bprm->envc = count(envp, max_arg_strings);
if ((retval = bprm->envc)
goto out_mm;
retval = copy_strings_kernel(1, &bprm->filename, bprm);//拷貝檔名稱
bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);//拷貝envc
if (retval
goto out;
env_p = bprm->p;
retval = copy_strings(bprm->argc, argv, bprm);//拷貝argc
if (retval
goto out;
bprm->argv_len = env_p - bprm->p;
retval = search_binary_handler(bprm,regs);
我們看到argc是怎麼算出來的:
bprm->argc = count(argv, max_arg_strings);
它實際上就是算出了引數的個數,下面最重要的就是copy_strings函式了,這個函式的意義就是將引數拷貝到乙個核心的頁面當中並設定為bprm的乙個字段,我們先看看bprm結構:
struct linux_binprm while (0)
ei_index += 2;
sp = stack_add(p, ei_index);
items = (argc + 1) + (envc + 1); //items就是引數的數量
if (interp_aout) else else {
argv = sp;
envp = argv + argc + 1;
p = current->mm->arg_start;
while (argc-- > 0) { //一次壓入argv引數的指標
size_t len;
__put_user((elf_addr_t)p, argv++);
len = strnlen_user((void __user *)p, page_size*max_arg_pages);
if (!len || len > page_size*max_arg_pages)
return;
p += len;
__put_user(0, argv);
current->mm->arg_end = current->mm->env_start = p;
while (envc-- > 0) {
size_t len;
__put_user((elf_addr_t)p, envp++);
len = strnlen_user((void __user *)p, page_size*max_arg_pages);
if (!len || len > page_size*max_arg_pages)
return;
p += len;
__put_user(0, envp);
current->mm->env_end = p;
sp = (elf_addr_t __user *)envp + 1;
copy_to_user(sp, elf_info, ei_index * sizeof(elf_addr_t));
要 想徹底明白這個機制,其實明白main的引數結構就可以了,看看第二個引數char *argv,實際上就是個指標的指標了,argv指向乙個陣列的頭指標,而此陣列的元素是字串,copy_strings拷貝的是各個字串的內容,在當時由於新的位址空間還未就位因此根本談不上指標,因為指標其實就是位址空間的乙個位址,後來到了create_elf_tables的時候,起碼引數相關的vma已經就位了,因此位址資訊就確定了,因此只有在這裡推入各個引數的指標資訊,而這些指標指向的就是copy_strings拷貝的內容,相應的指標值是通過引數vma的內部位址和引數數量算出來的。
這就是堆疊的好處,幫助一切函式呼叫傳遞引數,包括main函式(rom中無法呼叫函式就是因為rom不可寫,而操作堆疊必須寫記憶體)。linux將一切 策略留給使用者,僅僅幫助使用者推入了一系列main函式的引數,不光linux,windows也是這樣,不過windows除了這些,還幫使用者做了更多,包括把使用者煩死。
linux程式的命令列引數
程式執行的時候需要命令列引數,linux中更是這樣,隨便在shell輸入 bin xx help後列舉出來的引數讓你頭暈眼花,可是這些引數是怎麼進入程式的呢,我們知道程式執行的時候一般從main開始,而mian有兩個引數,乙個是 argc代表引數的個數,乙個是argv代表具體字串型別的引數,這是我們...
linux程式的命令列引數
程式執行的時候需要命令列引數,linux中更是這樣,隨便在shell輸入 bin xx help後列舉出來的引數讓你頭暈眼花,可是這些引數是怎麼進入程式的呢,我們知道程式執行的時候一般從main開始,而mian有兩個引數,乙個是 argc代表引數的個數,乙個是ar 代表具體字串型別的引數,這是我們所...
C C 程式的命令列引數
c 程式的main函式有兩個引數 int main int argc,char argv 我以前 大學學習的時候 一直沒有弄清楚這兩個引數的真正目的,一直到做linux c開發的時候,才知道,原來這兩個引數就是用來提供我們在linux的終端上執行某個命令時,攜帶的額外引數,例如 gcc o test...