I am currently learning System Programming and came across the usage of int system(const char* command)
in the chapter Process Management. They say that if a process is spawning a child, only to immediately wait for its temination, it is better to use system(const char* command).
What does it mean by the sentence "The command parameter is suffixed to the arguments /bin/sh -c. "
And how are fork(), exec(),waitpid() system calls associated with this?
CodePudding user response:
See the manpages for these calls:
From man 2 fork
: https://man7.org/linux/man-pages/man2/fork.2.html
fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.
From man 3 exec
: https://man7.org/linux/man-pages/man3/exec.3.html
The exec() family of functions replaces the current process image with a new process image.
From man 3 system
: https://man7.org/linux/man-pages/man3/system.3.html
The system() library function uses fork(2) to create a child process that executes the shell command specified in command using execl(3) as follows:
execl("/bin/sh", "sh", "-c", command, (char *) NULL);
system() returns after the command has been completed.
So system()
is a fast way to run a command on a shell which itself will use fork()
and exec()
to execute the given command.
Whereby fast only applies to programming effort. Using fork()
and exec()
directly is much faster in runtime.
CodePudding user response:
What does it mean by the sentence "The command parameter is suffixed to the arguments /bin/sh -c. "
That means that the entire string used with system() will be passed to
and executed by /bin/sh
which really can be anything but in majority
of cases it's some variant of POSIX-compliant shell such as Dash on
Ubuntu, Bash on Slackware, busybox ash on embedded Linux distros or
tcsh on FreeBSD. Since command is executed by shell you can use
commands in $PATH, shell builtins and shell constructs such as |
,
&&
, loops, redirections, command substitutions and so on. For
example you can do:
system("echo Date now: $(date) > /tmp/NOW");
just like you would do in your terminal emulator. Notice that system() starts non-interactive shell so you cannot use aliases and other features such as Bash history expansions that work only in interactive shells. Also notice that you have to get quoting right because the string you pass to system() will be first parsed by the C compiler and then by shell.
And how are fork(), exec(),waitpid() system calls associated with this?
system() can be considered an easy-to-use wrapper over fork(), exec() and waitpid(). You can write a simple main.c program:
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
system("echo Date now: $(date) > /tmp/NOW");
return EXIT_SUCCESS;
}
Compile it:
gcc main.c -o main -Wall -Wextra -pedantic
And run in under strace:
strace -f ./main
You'll see that ./main firsts forks the new process using clone() syscall (you'll not find fork() here):
clone(child_stack=0x7f7ed4a58ff0, flags=CLONE_VM|CLONE_VFORK|SIGCHLDstrace: Process 31782 attached
Then execs /bin/sh with specified arguments:
[pid 31782] execve("/bin/sh", ["sh", "-c", "echo Date now: $(date) > /tmp/NO"...], 0x7ffd91f10418 /* 61 vars */ <unfinished ...>
and then waits for it:
[pid 31781] wait4(31782, <unfinished ...>
If you feel fluent in C you can read libc source code to see how are wrappers such as system() implemented but Glibc that you most probably use on your Linux distro can be very hard to read and understand so oftentimes it's better to read musl libc source https://git.musl-libc.org/cgit/musl/tree/src/process/system.c (of course implementation between Glibc and other C libraries can differ but reading musl source code should give a generic understanding about how the given system can be implemented in Glibc).