Summary: In this section we will focus on the process environment. For example, how the program is started, how some of the process’s environment is retrieved, what the memory layout of the process is, and the various ways in which a process can be terminated.
1: How the program begins execution.
In general, we all know that we write C programs that start with the main function. Main takes argc and argv, where argc is the number of arguments and argv is an array of Pointers that store specific arguments. When we start executing the program, we call a startup routine. The startup routine reads the number command line arguments and environment variables from the kernel to arrange for the main function. Then launch the main function. Note: We can simply understand this process.Copy the code
2: Various ways to terminate a process.
There are eight ways in which a process can terminate, described here. (1) Return from main. (2) Call exit. Call _exit or _exit. (4) The last thread returns from the startup routine. (5) The last thread calls pthread_exit. (6) Call abort. (7) Abnormal termination of a signal is received. (8) The last thread responds to the cancellation request. Let's start with a picture that includes some of the process startup and exit processes. Note: We only discuss the user-specific part, not the kernel part.Copy the code
As you can see from the figure, returning from main is going back to the startup routine. Then we'll go back to the kernel. The only difference between (2) and (3) is that (2) does some cleanup and then returns to the kernel. (6) Abort: This function produces an abort signal and guarantees that the signal will not be terminated. At the same time, we can see from the diagram that there is a terminator. Next, we will introduce two terminating functions. Void exit(int status); int atexit(void (*func)(void)); The first function, which returns the state of the parent process, is already known. The second function is the one that registers the termination handler, which registers the function in the reverse order in which we call the termination handler. #include <stdio.h> #include <stdlib.h> #inculde <unistd.h> void exit1(); void exit2(); int main() { printf("test atexit"); atexit(exit1); atexit(exit2); exit(0); } void exit1() { printf("exit1\n"); } void exit2() { printf("exit2\n"); }Copy the code
The result is shown below
3: Earlier we mentioned that the startup routine inherits the environment table from the kernel, which prepares our program.
Here, let's take a brief look at the environment table. The environment table is stored in an array of Pointers, and there is an environment pointer in memory to the memory address of the environment table. The environment table is composed of name=value, as shown in the following figure. We typically use the purtenv and getenv functions to access and set environment variables in the environment table. char* getenv(const char* name); // This function searches for name in the environment table and returns a pointer to value in the environment table. int putenv(const char* name); // The argument part of this function is a string of the form name=value, which iterates through the environment table and sets the corresponding value of the environment table to our value.Copy the code
4: Next we will explain the storage space layout of C program.
C program storage space can be divided into the following parts: Body: the part where the CPU executes machine instructions. The location where the code is stored, from which the CPU reads and executes corresponding instructions. Initialize the data segment: this segment of memory indicates the part of the program that is explicitly assigned, such as the global variable int a = 10; Uninitialized data segment: This segment stores the portion of memory that is not explicitly initialized. Stack: An area for automatic variable allocation. For example, int a = 10; Heap: An area where the programmer manually allocates and frees memory. Here is a graph of memory allocation.Copy the code
Normally, we programmers assign functions manually using the following functions: malloc, Calloc, Realloc, and free. Please refer to the MAN manual for details on how these functions are used.Copy the code
5: The format of the environment variable is also the name=value format, we can use getenv and setenv functions to get and set the environment variable. The so-called environment variable I understand is to improve a program can use the environment defined variables.
6: Various limitations of the system.
Our computers have limited resources, and we need to run programs with limited resources. These limits have a maximum value and a default value. The maximum is our hardware limit. For example, if you have a computer with only 10 MB of memory, it's hard to get more than 10 MB of memory. There is also a software limitation that we can change. Eg: The number of file descriptors that a program can open is normally 1024, but can be changed to 2048 (depending on the computer). The following two functions relate to this limitation. int getrlimit(int resource, struct rlimit *rptr); int setrlimit(int resource, const struct* rlimit *rlptr); Struct rlimit structure is as follows. struct rlimit { rlim_t rlim_cur; /* Limitations on software */ rlim_t rlim_max; /* Hardware constraints */}; Note: our hardware shows that process 0 can only be changed during system initialization. It is then inherited by the processes. There are several rules for changing restrictions. (1) Any process can change the soft limit, but it must be smaller than the hard limit. (2) Any process can lower the value of the hard limit, but cannot change it to a larger value. (3) Only superuser processes can change hard limits. The various restrictions on computers are listed below.Copy the code