核心建立的使用者程序printf不能輸出一問的研究

2021-04-27 01:19:18 字數 4669 閱讀 6217

fork()與execve()中stderr,stdio.stdout的繼承關係

其實用繼承這個詞好像不太準確,要準確一點,可能複製更適合.

首先有二點:

1:父程序fork出子程序後,是共享所有檔案描述符的(實際上也包括socket)

2:程序在execve後,除了用o_cloexec標誌開啟的檔案外,其它的檔案描述符都是會複製到下個執行序列(注意這裡不會產生乙個新程序,只是將舊的程序替換了)

下面我們從**中找依據來論證以上的兩個觀點.

對於第一點:

我們在分析程序建立的時候,已經說過,如果父過程在建立子程序的時候帶了clone_files標誌的時候,會和父程序共享task->files.如果沒有定義,就會複製父程序的task->files.無論是哪種情況,父子程序的環境都是相同的.

**如下:

static int copy_files(unsigned long clone_flags, struct task_struct * tsk)

tsk->files = null;

newf = dup_fd(oldf, &error);

if (!newf)

goto out;

tsk->files = newf;

error = 0;

out:

return error;

}從上面的**可以看出.如果帶clone_files標誌,只是會增加它的引用計數.否則,開啟的檔案描符述會全部複製.

對於二:

static void flush_old_files(struct files_struct * files)

}spin_lock(&files->file_lock);

}spin_unlock(&files->file_lock);

}該函式會將重新整理舊環境的檔案描述符資訊.如果該檔案描述符在fdt->close_on_exec被置位,就將其關閉.

然後,我們來跟蹤一下,在什麼樣的情況下,才會將fdt->close_on_exec的相關位置1.

在sys_open() à get_unused_fd_flags():

int get_unused_fd_flags(int flags)

只有在帶o_cloexec開啟的檔案描述符,才會在execve()中被關閉.

三:使用者空間的stderr,stdio.stdout初始化

論證完上面的二個觀點之後,後面的就很容易分析了.我們先來分析一下,在使用者空間中,printf是可以使用的.哪它的stderr,stdio.stdout到底是從哪點來的呢?

我們知道,使用者空間的所有程序都是從init程序fork出來的.因此,它都是繼承了init程序的相關檔案描述符.

因此,問題都落在,init程序的stderr,stdio.stdout是在何時被設定的?

首先,我們來看一下核心中的第乙個程序.它所**的task_struct結構如下所示:

#define init_task(tsk) /

它所有的檔案描述符資訊都是在init_files中的,定義如下:

static struct files_struct init_files = init_files;

#define init_files /

},         /

.open_fds_init     = },               /

.fd_array =            /

}我們從這裡可以看到,核心的第一程序是沒有帶開啟檔案資訊的.

我們來看一下使用者空間的init程序的建立過程:

start_kernel() -à rest_init()中**片段如下:

static void noinline __init_refok rest_init(void)

__releases(kernel_lock)

該函式建立了兩個程序,然後本程序將做為idle程序在輪轉.

在建立kernel_init程序的時候,帶的引數是clone_fs | clone_sighand.它沒有攜帶clone_files標誌.也就是說,kernel_init中的檔案描述符資訊是從核心第一程序中複製過去的.並不和它共享.以後,kernel_init程序中,任何關於files的開啟,都不會影響到父程序.

然後在kernel_init() à init_post()中有:

static int noinline init_post(void)

從上面的**中可以看到,它先open了/dev/console.在open的時候,會去找程序沒使用的最小檔案序號.而,當前程序沒有開啟任何檔案,所以sys_open()的時候肯定會找到0.然後,兩次呼叫sys_dup(0)來複製檔案描述符0.複製後的檔案找述符肯定是1.2.這樣,0.1.2就建立起來了.

然後這個程序呼叫run_init_process() à kernel_execve()將當前程序替換成了使用者空間的乙個程序,這也就是使用者空間init程序的由來.此後,使用者空間的程序全是它的子孫程序.也就共享了這個0.1.2的檔案描述符了.這也就是我們所說的stderr.stdio,stdout.

從使用者空間寫個程式測試一下:

#include

#include

#include

#include

#include

main()

執行這個程式,我們會看到,0,1,2描述符的資訊全為/dev/consle.

四:核心建立使用者空間程序的過程

在核心中建立使用者空間程序的相應介面為call_usermodehelper().

實現上,它將要建立的程序資訊鏈入乙個工作佇列中,然後由工作佇列處理函式呼叫kernel_thread()建立乙個子程序,然後在這個程序裡呼叫kernel_execve()來建立使用者空間程序.

在這裡要注意工作佇列和下半部機制的差別.工作佇列是利用乙個核心程序來完成工作的,它和下半部無關.也就是說,它並不在乙個中斷環境中.

那就是說,這樣建立出來的程序,其實就是核心環境,它沒有開啟0,1.2的檔案描述符.

可能也有人會這麼說:那我就不在核心環境下建立使用者程序不就行了?

例如,我在init_module的時候,建立乙個核心執行緒,然後在這個核心執行緒裡,kernel_execve()乙個使用者空間程序不就可以了嗎?

的確,在這樣的情況下,建立的程序不是乙個核心環境,因為在呼叫init_module()的時候,已經通過系統呼叫進入kernel,這時的環境是對應使用者程序環境.但是別忘了.在系統呼叫環境下,再進行系統呼叫是不會成功的(kernel_execve也對應乙個系統呼叫.)

舉例印證如下:

mdoule**:

#include

#include

#include

#include

#include

#include

#include

module_license("gpl");

module_author( "ericxiao:[email protected]" );

static int exeuser_init()

;char *env =

;printk("exeuser_init .../n");

ret = call_usermodehelper(argv[0], argv, env,umh_wait_exec);

return 0;

}static int exeuser_exit()

module_init(exeuser_init);

module_exit(exeuser_exit);

使用者空間程式**:

#include

#include

#include

#include

#include

#include

int main(int argc,char *argv,char *env)

for(i=0; isprintf(printfmt,"echo arg[%d]:%s. >>/var/console",i,argv[i]);

system(printfmt);     

}tty = ttyname(0);

if(tty == null)

system("echo tty0 is null >> /var/console");

else

tty = ttyname(1);

if(tty == null)

system("echo tty1 is null >> /var/console");

else

tty = ttyname(2);

if(tty == null)

system("echo tty2 is null >> /var/console");

else

tty = ttyname(fd);

if(tty == null)

system("echo fd is null >> /var/console");

else

return 0;

}插入模組過後,呼叫使用者空間的程式,然後這個程式將程序環境輸出到/var/console中,完了可以看到.這個程序輸出的0,1,2描述符資訊全部null.

千萬要注意,在測試的使用者空間程式,不能開啟檔案.這樣會破壞該程序的原始檔案描述符環境(因為這個問題.狠調了乙個晚上,汗顏…).

這樣.使用者空間的printf當然就不能列印出東西了.

核心建立的使用者程序printf不能輸出一問的研究

close on exec init open fds init fd array 我們從這裡可以看到,核心的第一程序是沒有帶開啟檔案資訊的.我們來看一下使用者空間的init程序的建立過程 start kernel rest init 中 片段如下 static void noinline init...

核心建立的使用者程序printf不能輸出一問的研究

一 前言 上個星期同事無意間說起,在用核中建立的使用者空間程序中,使用printf不能顯示的問題.這個問題我當時一時半會沒有解釋清楚.現在就從linux kernel的源 的角度來分析該問題的原因所在.二 fork 與execve 中stderr,stdio.stdout的繼承關係 其實用繼承這個詞...

建立特定使用者的程序

用於建立特定使用者的程序,比如在服務程序中建立基於當前使用者的程序等等 bool gettokenbyname handle htoken,lptstr lpname handle hprocesssnap null bool bret false processentry32 pe32 pe32....