1

I have a simple doubt on execution of the command ls. As per my understanding from the research I have done on the internet, I understood the below points.

  1. When we type ls command shell interprets that command.

  2. Then the shell process forks and creates the child process and the parent (shell) executes the wait() system call, effectively putting itself to sleep until the child exits.

  3. Child process inherits all the open file descriptors and the environment.

  4. The child process (shell) executes an exec() of the ls program, causing the ls binary being loaded from the disk (filesystem) and being executed in the same process.

  5. When the ls program runs to completion, it calls exit(), and the kernel sends a signal to its parent indicating the child has terminated.

My doubt starts from here onwards, as soon as ls finishes its tasks; does it send the result back to the parent process, or does it display the output to the screen? If it sends the output back to parent, then is it using pipe() implicitly?

2
  • Child process inherits all the open file descriptors and the environment. Not to mention the PID, the process control group, the ulimits, the umask. The SELinux security context too (I think). Nov 28, 2023 at 19:29
  • @amphetamachine, the others yes (though they're not that relevant wrt. printing), but a child process gets a PID of its own (it's the process id, after all). The PID remains the same through exec(), though, but that happens within the same child process
    – ilkkachu
    Nov 28, 2023 at 19:44

4 Answers 4

2

ls will output what it has to output on its standard output. To do that, it calls the write system call, something like:

 write(1, "file1  file2...\n", 16)

(or more likely it calls libc functions like printf or fwrite that eventually do the write() system call)

It assumes that the file descriptor 1 (stdout by convention) was already opened and points to something. Actually, ls does check whether its file descriptor 1 points to a terminal or something else. If it doesn't point to a terminal, it does instead:

 write(1, "file1\nfile2...\n", 15)

That is, it writes one file per line when the output doesn't go to a terminal.

When you write:

 ls file1 file2

ls's file descriptor 1 will point to the same resource as the shell's fd 1 (so for instance, if it was an interactive shell started by xterm, that will point to the pseudo-terminal device controlled by xterm). The shell does nothing special, it's inherited upon fork and because the O_CLOEXEC flag is typically not set of the file descriptor, it is preserved upon execve.

If you write:

var=$(ls file1 file2)

The shell creates a pipe, and assignes the fd 1 of the child process to the writing end of that pipe and reads the other end of the pipe to fill the var variable.

It's not magically done upon exit, it's just done as part of the job of the process. It's independant from any other activity of the shell. ls is just another process with its fd 1 connected to some resource like the terminal while the shell is another process busy doing a waitpid().

What you may find though is that when stdout is not a terminal, ls buffers its output and would only call write() when enough data (enough being several kilobytes) have been accumulated or it's closing its output or it's exiting. So, in that regard, you'll find the write is done upon exiting, but only as part of the flushing of buffers that is being done by those I/O libraries.

0
1

Typically the parent process waits until the child process ends by calling waitpid. The parent process gets the PID of the process from fork.

This means the child never signals the parent process in any way that it exited or what happened. This is done by the system and not the child process.

If you are talking about the output of the program, the parent typically never receives the output of the child process unless it provided fds. This also means that the child process prints the output and not the parent process. The parent process just receives information about the state of the process (for more information see the macros in the waitpid manpage)

0
1

Strictly speaking neither of parent and child do the actual output to the screen; instead the operating system kernel (specifically the tty driver) does the output. The processes only send the data to a file descriptor.

Also, instead of speculating strace -f /bin/bash "ls;exit" clearly shows who is writing what to the file descriptors (BASH seems to optimize "-c ls" by not forking, so I used the ";exit" afterwards), like this (bash uses clone, not fork):

...
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f9814b5d9d0) = 30029
Process 30029 attached
[pid 30028] rt_sigprocmask(SIG_SETMASK, [],  <unfinished ...>
[pid 30029] rt_sigprocmask(SIG_SETMASK, [],  <unfinished ...>
...
[pid 30028] wait4(-1,  <unfinished ...>
...
[pid 30029] execve("/usr/bin/ls", ["ls"], [/* 72 vars */]) = 0
...
[pid 30029] write(1, "00708022-PTF-26962\nCASE00280714\n"..., 18500708022-PTF-26962) = 185
...
[pid 30029] exit_group(0)               = ?
[pid 30029] +++ exited with 0 +++
<... wait4 resumed> [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 30029
...

So you see that actually PID 30029 (the child) writes out the file names, exits, and then the parent continues (after wait4).

2
  • That's strace -f /bin/bash -c "ls;exit" -- add -c to get bash to execute a command. Otherwise the argument is taken as a script filename. Nov 28, 2023 at 19:32
  • Seems to depend on the version of BASH: "/usr/bin/ls" is not a script.
    – U. Windl
    Nov 29, 2023 at 10:01
0

If exec(3) succeeds, the program which called it is no more. It is replaced by the program execed. The new program running inherits the environment, in particular the open files, of the original.

5
  • > Could you pls help me in understanding the concept.My question is does the child sends the result back to the parent?who is printing the output of ls command.is it the child process or the shell,which is the parent process in this case.? Mar 14, 2013 at 18:33
  • 1
    Your shell fork(3)s, and the child then exec(3)s ls. While ls runs, it is the child process, which took over the child shell. The child ls writes directly to the screen, while the parent process wait(2)s for the child to finish before resuming. The first thing the parent does is print the prompt again.
    – vonbrand
    Mar 14, 2013 at 18:37
  • Thanks for clearing my doubt.If you dont mind shall i ask 1 more doubt,it was in my mind when i started learning pipes.Even the above doubt itself arise in my mind because of that root doubt..Can a pipe() be called implicitly in any case even if we dont use command like this ls | more.What i meant to ask exactly is if i execute a single command say for eg ls and assume that here child process sends data back to parent,then it need to use one or other way say pipe.Then does pipe() is called such scenarios implicitly even if we dont use "|" operator? Mar 14, 2013 at 18:44
  • There is no pipe from the child to the parent in this case. Both just happen to write to the same output file (screen). Unless you explicitly ask it to connect processes with |, the shell won't do it.
    – vonbrand
    Mar 14, 2013 at 18:52
  • @vondrand> Fine you cleared all my confusions.Thanks buddy ;-) Mar 14, 2013 at 18:54

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .