UNIX Advanced Environment Programming Learning Notes 6_ Process Control

1. Function fork

Function prototype:

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

The fork function creates a subprocess and returns it twice. Child process ID and 0, respectively. It is equivalent to copying the same process and executing it at the point where fork is called while the program is running. It has two main purposes:
1. Parent and child processes execute different code snippets, such as network servers that process information while listening for it.
2. A process execution requires a different new program to execute.

2. Function exit

1. Five ways for the program to terminate normally:

  • Call return in main function returns termination
  • Call exit directly to execute a series of termination handlers and end
  • Call_ exit function directly terminates the process
  • The last thread starts the return statement to return
  • Last thread calls pthread_exit function returns
    Where exit and _ The difference between exit is that the latter generally terminates directly without a series of processing
    Here you need to be aware of the state changes when the parent and child processes terminate. When the parent process terminates first, the child process is hosted by the init process. When the child process terminates, the parent process can know the termination state through the wait function.

3. Functions wait and waitpid

Function prototype:

#include<sys/wait.h>
pid_t wait(int *statloc);              /* *statloc Termination state pointing to process */
pid_t waitpid(pid_t pid, int *statloc, int options); /* pid Specify a process */

Wait blocks the caller and waits for the subroutine to return; Watpid controls whether the caller is blocked or not, and it can be used to wait for a specified subprocess termination signal.

4. Function exec

There are seven exec functions that create a new program and completely replace the program of the called process. Replacement, not replication, must be emphasized here, which is equivalent to the new program replacing the body, data, and stack spaces of the current process. At the same time, it can specify parameters and operating environment.
Function prototype:

#include<unistd.h>
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char const argv[0]);
int execle(const chat *pathname, const char *arg0, ... /* (char *)0 , char *const envp[] */) 
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const chat *arg0, ... /* (char *)0 */);
int execvp(const chat *filename, char *const argv[]);
int fexecve(int fd, char *const argv[], char *const envp[]);

1.Of the seven exec functions, only execve is a system call. The other functions are implemented through execve calls.
2. In the prototype of the appeal function, the first is a new program to run, followed by parameters, and the new program starts from the main function.
3. The final end flag is *(char) 0.
Example function use (subprocess running echoall program):
Main program:

#include "apue.h"
#include <sys/wait.h>

char	*env_init[] = { "USER=unknown", "PATH=/tmp", NULL };

int
main(void)
{
	pid_t	pid;

	if ((pid = fork()) < 0) {
		err_sys("fork error");
	} else if (pid == 0) {	/* specify pathname, specify environment */
		if (execle("/home/sar/bin/echoall", "echoall", "myarg1",
				"MY ARG2", (char *)0, env_init) < 0)
			err_sys("execle error");
	}

	if (waitpid(pid, NULL, 0) < 0)
		err_sys("wait error");

	if ((pid = fork()) < 0) {
		err_sys("fork error");
	} else if (pid == 0) {	/* specify filename, inherit environment */
		if (execlp("echoall", "echoall", "only 1 arg", (char *)0) < 0)
			err_sys("execlp error");
	}

	exit(0);
}

echoall program:

#include "apue.h"

int
main(int argc, char *argv[])
{
	int			i;
	char		**ptr;
	extern char	**environ;

	for (i = 0; i < argc; i++)		/* echo all command-line args */
		printf("argv[%d]: %s\n", i, argv[i]);

	for (ptr = environ; *ptr != 0; ptr++)	/* and all env strings */
		printf("%s\n", *ptr);

	exit(0);
}

4. The difference between an actual user ID and a valid user ID

Actual user ID and valid user ID in Unix (good explanation)

5. Function system

Function prototype:

#include<stdlib.h>
int system(const char *cmdstring);

The implementation of this function calls fork, exec, waitpid and other functions. One way to do this in the book is as follows:

#include	<sys/wait.h>
#include	<errno.h>
#include	<unistd.h>

int
system(const char *cmdstring)	/* version without signal handling */
{
	pid_t	pid;
	int		status;

	if (cmdstring == NULL)
		return(1);		/* always a command processor with UNIX */

	if ((pid = fork()) < 0) {
		status = -1;	/* probably out of processes */
	} else if (pid == 0) {				/* child */
		execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);  /*Switch to shell program*/
		_exit(127);		/* execl error */
	} else {							/* parent */
		while (waitpid(pid, &status, 0) < 0) {
			if (errno != EINTR) {
				status = -1; /* error other than EINTR from waitpid() */
				break;
			}
		}
	}

	return(status);
}

Six. Process Scheduling

1. Process Priority Change Function

#include<unistd.h>
int nice(int incr);    //Increase incr number under current priority, actually lower priority
//Successfully returned the current new NZERO value, with an error returning -1

#include<sys/resource.h>
int getpriority(int which, id_t who);  //which optional processes, process groups, and users; who represents the call value
int setpriority(int which, id_t who, int value);  //Set the specified priority

Here is an example to illustrate the impact of process priority:
First measure the size of the count value when the parent and child parallel processes are counted separately, then change the priority of the child process through the nice function to observe the size change of the count value when the priorities are different. The code is as follows:

#include "apue.h"
#include <errno.h>
#include <sys/time.h>

#if defined(MACOS)//Judgement System
#include <sys/syslimits.h>
#elif defined(SOLARIS)
#include <limits.h>
#elif defined(BSD)
#include <sys/param.h>
#endif

unsigned long long count;
struct timeval end;
long this_pid;

void checktime(char *str)
{
	struct timeval	tv;

	gettimeofday(&tv, NULL);               //Get the current time
	if (tv.tv_sec >= end.tv_sec && tv.tv_usec >= end.tv_usec) {  //Compare with preset time
		printf("%s count = %lld\n", str, count);
		exit(0);
	}
}

int main(int argc, char *argv[])
{
	pid_t	pid;
	char	*s;
	int		nzero, ret;
	int		adj = 0;

	setbuf(stdout, NULL);     //Output Bufferless
#if defined(NZERO)//Determine if the NZERO value is preset, that is, the preset priority
	nzero = NZERO;
#elif defined(_SC_NZERO)
	nzero = sysconf(_SC_NZERO);
#else
#error NZERO undefined
#endif
	printf("NZERO = %d\n", nzero);
	if (argc == 2)
		adj = strtol(argv[1], NULL, 10);
	gettimeofday(&end, NULL); 
	end.tv_sec += 10;	/* run for 10 seconds */

	if ((pid = fork()) < 0) {
		err_sys("fork failed");
	} else if (pid == 0) {	/* child */
		s = "child";
		this_pid = (long)getpid();
		printf("current nice value in child is %d, adjusting by %d\n",
		  nice(0)+nzero, adj);
		errno = 0;
		if ((ret = nice(adj)) == -1 && errno != 0)
			err_sys("child set scheduling priority");
		printf("now child nice value is %d\n", ret+nzero);
	} else {		/* parent */
		s = "parent";
		this_pid = (long)getpid();
		printf("current nice value in parent is %d\n", nice(0)+nzero);
	}
	for(;;) {
		if (++count == 0)
			err_quit("%s counter wrap", s);
		printf("PID of %s : %d", s, this_pid);      //Observing how parallel programs run
		checktime(s);
	}
}

We observe the operation of child processes by assigning them different NZERO values. At the same time, compared to the method in the book, the code above also prints the specific scheduling of the current program when it runs:
(1) When both parent and child processes have priority of 20, the scheduling priority of the two processes is similar. Subprocesses account for 49.7% of the CPU.

(2) When the parent process is still 20, we add 10 to the child process's NZERO. At this point, the priority of the child process decreases, and the parent process runs only once for almost 19 times. The CPU utilization of the child process is reduced to about 8%.

From the above experiments, it is conjectured that the implementation of priority in ubuntu depends on how many time slices are allocated to each process.

Tags: C Linux

Posted by hoopplaya4 on Sun, 15 May 2022 22:01:01 +0300