The origin of stdin stdout stderr in Linux

Source: Internet
Author: User

Now we can analyze the source code of Linux kernel.
Ii. inheritance between fork () and stderr and stdio. stdout in execve ()
In fact, it seems that the word "inherit" is not accurate. To be more accurate, replication may be more appropriate.
First, there are two points:
1: the parent process fork shares all file descriptors after the child process exists (including socket)
2: After a process runs execve, all file descriptors except the files opened with the o_cloexec mark will be copied to the next execution sequence (note that no new process will be generated here, only replaced the old process)
The following describes the two points in the code.
For the first point:
When we analyze the creation of a process, we have already said that if the clone_files flag is included in the creation of a child process in the parent process, the task-> files will be shared with the parent process. if not defined, the task> files of the parent process will be copied. in either case, the environment of the Parent and Child processes is the same.
The Code is as follows:
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;
}
From the code above, we can see that if the clone_files flag is included, it will only increase its reference count. Otherwise, all opened file descriptors will be copied.
For example:
We have also analyzed sys_execve () before. If you are not familiar with sys_execve (), go to this site to find relevant articles and read them. The entire process is not described here. The relevant code is as follows:
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 );
}
This function will refresh the file descriptor information of the old environment. If the file descriptor is set in FDT-> close_on_exec, it will be disabled.
Then, let's track under what circumstances the FDT-> close_on_exec location will be 1.
In 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 );
......
}
The file descriptor opened with o_cloexec is disabled in execve.
3. initialize the stderr and stdio. stdout of the user space.
After the two points above are demonstrated, it is easy to analyze them later. let's analyze it first. In user space, printf can be used. stderr, stdio. where did stdout come from?
We know that all the processes in the user space come from the INIT process fork. Therefore, they all inherit the file descriptors of the INIT process.
As a result, all problems occur. When is stderr and stdio. stdout of the init process set?
First, let's take a look at the first process in the kernel. The task_struct structure of its code is as follows:
# 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,
......
}
All its file descriptor information is in init_files and is defined as follows:
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 ,}
}
We can see from this that the first process of the kernel does not contain information about opening files.
Let's take a look at the process of creating the INIT process in the user space:
The code snippets in start_kernel ()-à rest_init () are as follows:
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 ();
}
This function creates two processes and the process is rotated as an idle process.
When creating the kernel_init process, the parameter is clone_fs | clone_sighand. it does not carry the clone_files flag. that is to say, the file descriptor information in kernel_init is copied from the first process of the kernel. it is not shared with it. in the kernel_init process, any opening of files will not affect the parent process.
Then there are:
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 );
}
From the code above, we can see that it opened/dev/console first. when the process is open, the minimum file number that is not used by the process will be searched. however, the current process does not open any files, so sys_open () will certainly find 0. then, sys_dup (0) is called twice to copy the file descriptor 0. the copied file description must be 1. 2. in this way, 0.1.2 is set up.
Then this process calls run_init_process () à kernel_execve () to replace the current process with a process in the user space, which is the origin of the user space INIT process. since then, the user space process is all its child processes. the 0.1.2 file descriptor is shared. this is what we call stderr. stdio, stdout.
Write a program from the user space to test:
# 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;
}
Run this program. We can see that the information of the 0, 1, and 2 descriptors is/dev/consystemic.
4. process of creating a user space process using the kernel
The corresponding interface for creating a user space process in the kernel is call_usermodehelper ().
Implementation: The process information to be created is linked to a work queue. Then, the work queue processing function calls kernel_thread () to create a sub-process, and then calls kernel_execve () in the process () to create a user space process.
Pay attention to the difference between the working queue and the lower half mechanism. The working queue uses a kernel process to complete the work. It has nothing to do with the lower half. That is to say, it is not in an interrupted environment.
That is to say, the process created in this way is actually the kernel environment. It does not open the file descriptor 0, 1.2.
Some people may say this: I will not create a user process in the kernel environment?
For example, when I create a kernel thread in the init_module, and then in this kernel thread, can a user space process run kernel_execve?
Indeed, in this case, the created process is not a kernel environment, because when init_module () is called, the environment corresponds to the user process environment. but don't forget. in the system call environment, the system call will not be successful (kernel_execve also corresponds to a system call .)
Examples are as follows:
Mdoule code:
# 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 );
User space program code:
# 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;
}
After the module is inserted, the user space program is called, and then the process environment is output to/var/console. After the process is finished, we can see that all the 0, 1, 2 descriptor information output by this process is null.
Please note that the user space program in the test cannot open the file. This will damage the original file descriptor environment of the process (because of this problem, I have been calling it for one night, and I am ashamed of myself ...).
In this way, the printf of the user space cannot print anything.

 

PS: this old man's post dispelled some of my doubts. I have a general understanding of how printf works on the screen. In fact, I don't understand how to print messages through serial ports in embedded systems. I think we should change/dev/console to our serial port device file during the startup process, for example,/dev/tq2440seris0. In this way, it will be printed from the serial port 0 during printf.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.