“This is the 22nd day of my participation in the November Gwen Challenge. See details of the event: The Last Gwen Challenge 2021”.

1. fork

#include <unistd.h> 
pid_t fork(void);
Copy the code

The child copies the parent’s 0 to 3g space and the PCB in the parent’s kernel, but with a different ID number. The fork call returns twice once

  • The child process ID is returned from the parent process
  • Returns 0 in child process
  • Read to share, write to copy
#include <sys/types.h> 
#include <unistd.h> 
#include <stdio.h> 
#include <stdlib.h>
int main(void) {
	pid_t pid; 
	char *message; 
	int n;
	pid = fork(); 
	if (pid < 0) {
		perror("fork failed");
		exit(1); 
	}
	if (pid == 0) {
		message = "This is the child\n"; 
		n = 6;
	} else {
		message = "This is the parent\n"; 
		n = 3;
	}
	for(; n > 0; n--) {
		printf(message);
		sleep(1); 
	}
	return 0; 
 }
Copy the code

1.1 Process-related functions

#include <sys/types.h> #include <unistd.h> pid_t getpid(void); Pid_t getppId (void); // Returns the PID of the calling process's parentCopy the code

getpid/gteppid

#include <unistd.h> #include <sys/types.h> uid_t getuid(void); // Return the actual user ID uid_t geteuid(void); // Returns a valid user IDCopy the code

getuid

#include <unistd.h> #include <sys/types.h> gid_t getgid(void); Gid_t getegid(void); // Returns a valid user group IDCopy the code

getgid

vfork

  • Use to call exec immediately after fork
  • Parent and child processes share the same address space. If the child process does not immediately exec but modifies the value of a variable generated by the parent process, the change takes effect in the parent process
  • Designed to improve system efficiency and reduce unnecessary overhead
  • Now that fork has a read-share-write copy-write mechanism, vfork has fallen into disuse

2. The exec family

A child created with fork executes the same program as its parent (but possibly a different branch of code), often calling an exec function to execute another program. When a process calls an exec function, the process’s user-space code and data are completely replaced by the new program, starting with the startup routine of the new program. Calling exec does not create a new process, so the id of the process does not change before and after the exec call.

There are actually six types of functions that begin with exec, collectively called exec functions:

#include <unistd.h> int execl(const char *path, const char *arg, ...) ; int execlp(const char *file, const char *arg, ...) ; int execle(const char *path, const char *arg, ... , char *const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *path, char *const argv[], char *const envp[]);Copy the code

These functions load a new program and execute from the start code if they are called successfully, and return no more if they are called incorrectly. Therefore, the exec function has only the return value of the error and no return value of the success.

These function prototypes look easy to mess with, but are easy to remember once you get the hang of them. The first argument to an exec function without the letter P (for path) must be a relative or absolute path to the program, such as “/bin/ls” or “./ a.out “, not “ls” or “a.out”. For functions with the letter p:

If the parameter contains /, it is treated as the pathname. Otherwise, consider the program name without a PATH, and search for the program in the directory list of the PATH environment variable. The exec function with the letter L (for list) requires that each command-line argument of the new program be passed as an argument

Give it, the number of command-line arguments is variable, so the function prototype has… . The last variable parameter in should be NULL, acting as sentinel. For functions with the letter v(for vector), we should construct an array of Pointers to each argument and pass it the first address of the array as an argument. The last pointer in the array should also be NULL, just like the argv argument in main or the environment variable table.

For an exec function ending in e(for environment), you can pass it a new table of environment variables, and the other exec function still uses the current table of environment variables to execute the new program.

An example of an exec call is as follows:

char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL}; 
char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL}; 
execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL); 
execv("/bin/ps", ps_argv);
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp); execve("/bin/ps", ps_argv, ps_envp);
execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
execvp("ps", ps_argv);
Copy the code

In fact, only execve is a true system call. The other five functions all end up calling execve, so execve is in section 2 of the MAN manual, and the other functions are in section 3 of the MAN manual. The relationship between these functions is shown below.

A complete example:

#include <unistd.h> 
#include <stdlib.h>
int main(void) {
	execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL); 
	perror("exec ps");
	exit(1);
}
Copy the code

Since the exec function only returns error values, if it returns an error, it does not need to determine its return value, and can directly call perror later. Notice that two “ps” arguments are passed when execlp is called. The first “ps” is the program name, and the second “ps” is the first command line argument. Execlp doesn’t care about its value, it simply passes it to the PS program. The ps program can take this argument from argv[0] of main.

After the exec call, the previously open file descriptor remains open. This allows for I/O redirection. Let’s start with a simple example of converting standard input to uppercase and printing it to standard output:

Cases of upper

/* upper.c */ #include <stdio.h> int main(void) { int ch; while((ch = getchar()) ! = EOF) { putchar(toupper(ch)); } return 0; }Copy the code

Cases of wrapper

/* wrapper.c */ #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <fcntl.h> int main(int argc, char *argv[]) { int fd; if (argc ! = 2) { fputs("usage: wrapper file\n", stderr); exit(1); } fd = open(argv[1], O_RDONLY); if(fd<0) { perror("open"); exit(1); } dup2(fd, STDIN_FILENO); close(fd); execl("./upper", "upper", NULL); perror("exec ./upper"); exit(1); }Copy the code

The Wrapper program opens the command-line arguments as file names, redirects standard input to the file, and then calls exec upper, where the previously opened file descriptor remains open. Upper is only responsible for reading characters in uppercase from standard input, regardless of whether the standard input corresponds to a file or a terminal. The running results are as follows:

  • L Command line parameter list
  • P Use the path variable when searching for file
  • V Uses the command line argument array
  • E Use an array of environment variables to set the environment variables for the new loader to run without using the original environment variables of the process

3. Summary

This article introduced process primitives: fork and exec. The fork call returns the child ID twice: the parent process returns the child ID; Returns 0 in child process; Read to share, write to copy.