国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
內(nèi)核創(chuàng)建的用戶進程printf不能輸出一問的研究
一:前言
上個星期同事無意間說起,在用核中創(chuàng)建的用戶空間進程中,使用printf不能顯示的問題.這個問題我當時一時半會沒有解釋清楚.現(xiàn)在就從linux kernel的源代碼的角度來分析該問題的原因所在.
二:fork()與execve()中stderr,stdio.stdout的繼承關(guān)系
其實用繼承這個詞好像不太準確,要準確一點,可能復制更適合.
首先有二點:
1:父進程fork出子進程后,是共享所有文件描述符的(實際上也包括socket)
2:進程在execve后,除了用O_CLOEXEC標志打開的文件外,其它的文件描述符都是會復制到下個執(zhí)行序列(注意這里不會產(chǎn)生一個新進程,只是將舊的進程替換了)
下面我們從代碼中找依據(jù)來論證以上的兩個觀點.
對于第一點:
我們在分析進程創(chuàng)建的時候,已經(jīng)說過,如果父過程在創(chuàng)建子進程的時候帶了CLONE_FILES標志的時候,會和父進程共享task->files.如果沒有定義,就會復制父進程的task->files.無論是哪種情況,父子進程的環(huán)境都是相同的.
代碼如下:
static int copy_files(unsigned long clone_flags, struct task_struct * tsk)
{
     struct files_struct *oldf, *newf;
     int error = 0;
     oldf = current->files;
     if (!oldf)
         goto out;
 
     if (clone_flags & CLONE_FILES) {
         atomic_inc(&oldf->count);
         goto out;
     }
 
     tsk->files = NULL;
     newf = dup_fd(oldf, &error);
     if (!newf)
         goto out;
 
     tsk->files = newf;
     error = 0;
out:
     return error;
}
從上面的代碼可以看出.如果帶CLONE_FILES標志,只是會增加它的引用計數(shù).否則,打開的文件描符述會全部復制.
 
對于二:
我們之前同樣也分析過sys_execve().如果有不太熟悉的,到本站找到相關(guān)文章進行閱讀.在這里不再詳細說明整個流程.相關(guān)代碼如下:
 
static void flush_old_files(struct files_struct * files)
{
     long j = -1;
     struct fdtable *fdt;
 
     spin_lock(&files->file_lock);
     for (;;) {
         unsigned long set, i;
 
         j++;
         i = j * __NFDBITS;
         fdt = files_fdtable(files);
         if (i >= fdt->max_fds)
              break;
         set = fdt->close_on_exec->fds_bits[j];
         if (!set)
              continue;
         fdt->close_on_exec->fds_bits[j] = 0;
         spin_unlock(&files->file_lock);
         for ( ; set ; i++,set >>= 1) {
              if (set & 1) {
                   sys_close(i);
              }
         }
         spin_lock(&files->file_lock);
 
     }
     spin_unlock(&files->file_lock);
}
該函數(shù)會將刷新舊環(huán)境的文件描述符信息.如果該文件描述符在fdt->close_on_exec被置位,就將其關(guān)閉.
然后,我們來跟蹤一下,在什么樣的情況下,才會將fdt->close_on_exec的相關(guān)位置1.
在sys_open() à get_unused_fd_flags():
int get_unused_fd_flags(int flags)
{
     ……
     …….
if (flags & O_CLOEXEC)
         FD_SET(fd, fdt->close_on_exec);
     else
         FD_CLR(fd, fdt->close_on_exec);
……
}
只有在帶O_CLOEXEC打開的文件描述符,才會在execve()中被關(guān)閉.
 
三:用戶空間的stderr,stdio.stdout初始化
論證完上面的二個觀點之后,后面的就很容易分析了.我們先來分析一下,在用戶空間中,printf是可以使用的.哪它的stderr,stdio.stdout到底是從哪點來的呢?
我們知道,用戶空間的所有進程都是從init進程fork出來的.因此,它都是繼承了init進程的相關(guān)文件描述符.
因此,問題都落在,init進程的stderr,stdio.stdout是在何時被設(shè)置的?
 
首先,我們來看一下內(nèi)核中的第一個進程.它所代碼的task_struct結(jié)構(gòu)如下所示:
#define INIT_TASK(tsk)
{                                        
     .state        = 0,                       
     .stack        = &init_thread_info,                
     .usage        = ATOMIC_INIT(2),               
     .flags        = 0,                       
     .lock_depth   = -1,                           
     .prio         = MAX_PRIO-20,                      
     .static_prio  = MAX_PRIO-20,                      
     .normal_prio  = MAX_PRIO-20,                      
     .policy       = SCHED_NORMAL,                     
     .cpus_allowed = CPU_MASK_ALL,                     
     …….
     .files        = &init_files,                      
     ……
}
它所有的文件描述符信息都是在init_files中的,定義如下:
static struct files_struct init_files = INIT_FILES;
#define INIT_FILES
{                               
     .count        = ATOMIC_INIT(1),     
     .fdt     = &init_files.fdtab,       
     .fdtab        = INIT_FDTABLE,            
     .file_lock    = __SPIN_LOCK_UNLOCKED(init_task.file_lock),
     .next_fd = 0,                  
     .close_on_exec_init = { { 0, } },        
     .open_fds_init     = { { 0, } },              
     .fd_array = { NULL, }           
}
我們從這里可以看到,內(nèi)核的第一進程是沒有帶打開文件信息的.
我們來看一下用戶空間的init進程的創(chuàng)建過程:
Start_kernel() -à rest_init()中代碼片段如下:
static void noinline __init_refok rest_init(void)
     __releases(kernel_lock)
{
     int pid;
 
     kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
     numa_default_policy();
     pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
     kthreadd_task = find_task_by_pid(pid);
     unlock_kernel();
 
     /*
      * The boot idle thread must execute schedule()
      * at least once to get things moving:
      */
     init_idle_bootup_task(current);
     preempt_enable_no_resched();
     schedule();
     preempt_disable();
 
     /* Call into cpu_idle with preempt disabled */
     cpu_idle();
}
該函數(shù)創(chuàng)建了兩個進程,然后本進程將做為idle進程在輪轉(zhuǎn).
在創(chuàng)建kernel_init進程的時候,帶的參數(shù)是CLONE_FS |CLONE_SIGHAND.它沒有攜帶CLONE_FILES標志.也就是說,kernel_init中的文件描述符信息是從內(nèi)核第一進程中復制過去的.并不和它共享.以后,kernel_init進程中,任何關(guān)于files的打開,都不會影響到父進程.
 
然后在kernel_init() à init_post()中有:
static int noinline init_post(void)
{
 &n
bsp;   ……
     ……
if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
         printk(KERN_WARNING "Warning: unable to open an initial console.\n");
 
     (void) sys_dup(0);
     (void) sys_dup(0);
     ……
     ……
run_init_process(XXXX);
}
從上面的代碼中可以看到,它先open了/dev/console.在open的時候,會去找進程沒使用的最小文件序號.而,當前進程沒有打開任何文件,所以sys_open()的時候肯定會找到0.然后,兩次調(diào)用sys_dup(0)來復制文件描述符0.復制后的文件找述符肯定是1.2.這樣,0.1.2就建立起來了.
然后這個進程調(diào)用run_init_process() àkernel_execve()將當前進程替換成了用戶空間的一個進程,這也就是用戶空間init進程的由來.此后,用戶空間的進程全是它的子孫進程.也就共享了這個0.1.2的文件描述符了.這也就是我們所說的stderr.stdio,stdout.
 
從用戶空間寫個程序測試一下:
 
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
main()
{
         int ret;
        char *ttyname0,*ttyname1,*ttyname2;
 
      
        ttyname0 = ttyname(0);
        ttyname1 = ttyname(1);
        ttyname2 = ttyname(2);
        
         printf(“file0 : %s\n”,ttyname0);
         printf(“file1 : %s\n”,ttyname1);
         printf(“file2 : %s\n”,ttyname2);
 
        return;
}
運行這個程序,我們會看到,0,1,2描述符的信息全為/dev/consle.
 
四:內(nèi)核創(chuàng)建用戶空間進程的過程
在內(nèi)核中創(chuàng)建用戶空間進程的相應(yīng)接口為call_usermodehelper().
實現(xiàn)上,它將要創(chuàng)建的進程信息鏈入一個工作隊列中,然后由工作隊列處理函數(shù)調(diào)用kernel_thread()創(chuàng)建一個子進程,然后在這個進程里調(diào)用kernel_execve()來創(chuàng)建用戶空間進程.
在這里要注意工作隊列和下半部機制的差別.工作隊列是利用一個內(nèi)核進程來完成工作的,它和下半部無關(guān).也就是說,它并不在一個中斷環(huán)境中.
 
那就是說,這樣創(chuàng)建出來的進程,其實就是內(nèi)核環(huán)境,它沒有打開0,1.2的文件描述符.
可能也有人會這么說:那我就不在內(nèi)核環(huán)境下創(chuàng)建用戶進程不就行了?
例如,我在init_module的時候,創(chuàng)建一個內(nèi)核線程,然后在這個內(nèi)核線程里,kernel_execve()一個用戶空間進程不就可以了嗎?
的確,在這樣的情況下,創(chuàng)建的進程不是一個內(nèi)核環(huán)境,因為在調(diào)用init_module()的時候,已經(jīng)通過系統(tǒng)調(diào)用進入kernel,這時的環(huán)境是對應(yīng)用戶進程環(huán)境.但是別忘了.在系統(tǒng)調(diào)用環(huán)境下,再進行系統(tǒng)調(diào)用是不會成功的(kernel_execve也對應(yīng)一個系統(tǒng)調(diào)用.)
舉例印證如下:
Mdoule代碼:
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/serial_core.h>
#include <linux/kmod.h>
#include <linux/file.h>
#include <linux/unistd.h>
 
MODULE_LICENSE("GPL");
MODULE_AUTHOR( "ericxiao:xgr178@163.com" );
 
 
 
 
static int exeuser_init()
{
     int ret;
 
     char *argv[] =
     {
         "/mnt/hgfs/vm_share/user_test/main",
         NULL,
     };
 
     char *env[] =
     {
         "HOME=/",
         "PATH=/sbin:/bin:/usr/sbin:/usr/bin",
         NULL,
     };
 
     printk("exeuser_init ...\n");
     ret = call_usermodehelper(argv[0], argv, env,UMH_WAIT_EXEC);
 
     return 0;
}
 
static int exeuser_exit()
{
     printk("exeuser_exit ...\n");
     return 0;
}
 
module_init(exeuser_init);
module_exit(exeuser_exit);
 
用戶空間程序代碼:
#include <stdio.h>
#include <stdlib.h>   
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
 
int main(int argc,char *argv[],char *env[])
{
     int i;
     int fd;
     int size;
     char *tty;
     FILE *confd;
     char printfmt[4012];
 
     system("echo i am coming > /var/console");
     for(i=0; env[i]!=NULL;i++){
         sprintf(printfmt,"echo env[%d]:%s. >>/var/console",i,env[i]);
         system(printfmt);
     }
 
     for(i=0; i<argc ;i++){
         sprintf(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{
         sprintf(printfmt,"echo ttyname0 %s. >>/var/console",tty);
         system(printfmt); 
     }
 
     tty = ttyname(1);
     if(tty == NULL)
         system("echo tty1 is NULL >> /var/console");
     else{
         sprintf(printfmt,"echo ttyname1 %s. >>/var/console",tty);
         system(printfmt); 
     }
 
     tty = ttyname(2);
     if(tty == NULL)
         system("echo tty2 is NULL >> /var/console");
     else{
         sprintf(printfmt,"echo ttyname2 %s. >>/var/console",tty);
         system(printfmt); 
     }
 
     tty = ttyname(fd);
     if(tty == NULL)
         system("echo fd is NULL >> /var/console");
     else{
         sprintf(printfmt,"echo fd %s. >>/var/console",tty);
         system(printfmt); 
     }
 
    
 
     return 0;
}
插入模塊過后,調(diào)用用戶空間的程序,然后這個程序?qū)⑦M程環(huán)境輸出到/var/console中,完了可以看到.這個進程輸出的0,1,2描述符信息全部NULL.
千萬要注意,在測試的用戶空間程序,不能打開文件.這樣會破壞該進程的原始文件描述符環(huán)境(因為這個問題.狠調(diào)了一個晚上,汗顏…).
這樣.用戶空間的printf當然就不能打印出東西了.
 
四:小結(jié)
一個小問題.卻能引申這么多東西,看來以后不能放過工作和學習中的任何一個問題.刨根到底,總會有收獲的.一:前言

本站僅提供存儲服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
交互shell設(shè)置為/dev/console之后提示job control turned off(上)
Linux內(nèi)核源代碼漫游-執(zhí)行
linux下/dev/tty, /dev/tty0, /dev/console區(qū)別
linux kernel下輸入輸出console如何實現(xiàn)
守護進程實例
你的鍵盤是什么時候生效的?
更多類似文章 >>
生活服務(wù)
分享 收藏 導長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服