本文共 7783 字,大约阅读时间需要 25 分钟。
现在就从linux kernel的源代码的角度来分析该问题
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标志,只是会增加它的引用计数.否则,打开的文件描符述会全部复制. 对于二: 我们之前同样也分析过sys_execve().如果有不太熟悉的,到本站找到相关文章进行阅读.在这里不再详细说明整个流程.相关代码如下:
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);}该函数会将刷新旧环境的文件描述符信息.如果该文件描述符在fdt->close_on_exec被置位,就将其关闭. 然后,我们来跟踪一下,在什么样的情况下,才会将fdt->close_on_exec的相关位置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()中被关闭.
#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, } }我们从这里可以看到,内核的第一进程是没有带打开文件信息的. 我们来看一下用户空间的init进程的创建过程: 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(); init_idle_bootup_task(current); preempt_enable_no_resched(); schedule(); preempt_disable(); cpu_idle();}该函数创建了两个进程,然后本进程将做为idle进程在轮转. 在 创建kernel_init进程的时候,带的参数是CLONE_FS | CLONE_SIGHAND.它没有携带CLONE_FILES标志.也就是说,kernel_init中的文件描述符信息是从内核第一进程中复制过去的.并不和它共享.以后,kernel_init进程中,任何关于files的打开,都不会影响到父进程. 然后在kernel_init()->init_post()中有:
static int noinline init_post(void){ …… ……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.然后,两次调用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(){ 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.
#include用户空间程序代码:#include #include #include #include #include #include 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插入模块过后,调用用户空间的程序,然后这个程序将进程环境输出到/var/console中,完了可以看到.这个进程输出的0,1,2描述符信息全部NULL. 千万要注意,在测试的用户空间程序,不能打开文件.这样会破坏该进程的原始文件描述符环境(因为这个问题.狠调了一个晚上,汗颜…). 这样.用户空间的printf当然就不能打印出东西了. ps:这位老兄的帖子解了我的一些疑惑。大致了解了printf怎么会在屏幕上显示的原理。其实我是不明白在嵌入式系统中,如何通过串口打印一些消息的。我想应该是在启动过程中将/dev/console改成我们的串口设备文件,比如/dev/tq2440seris0,这样,在printf时就会从串口0打印。#include #include #include #include #include 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 >/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;}
转载地址:http://udsqb.baihongyu.com/