C programming practices in Linux (3 )?? Process control and process communication Programming 1. Linux process contains three data parts in the memory: code segment, stack segment, and data segment. The code segment stores the code of the program. Code segments can be shared by several processes running the same program on the machine. Stack Segment...
C programming practices in Linux (III)
?? Process control and process communication programming
1. Linux process
A Linux process contains three parts of data in the memory: code segment, stack segment, and data segment. The code segment stores the code of the program. Code segments can run multiple
Process sharing. The stack segment stores the return address of the subroutine (function), the parameters of the subroutine, and the local variables of the program. The data segment stores the global variables, constants, and dynamic data space allocated by the program (for example, the memory applied by the malloc function ). Unlike code segments, if multiple identical programs run simultaneously in the system, they cannot use the same stack segment or data segment.
Linux processes are in the following states: User status (the state in which the process runs in the user State) and kernel status (the state in which the process runs in the kernel state), memory ready (the process is not executed, but in the ready state, as long as the kernel schedules it, it can be executed), memory sleep (the process is sleeping and in the memory, not switched to the SWAP device), ready, and switched out (the process is in the ready state, but it must be switched into the memory before the kernel can schedule it to run again), sleep and switch out (the process is sleeping and the memory is swapped out), is preemptive (when the process returns the user state from the kernel state, the kernel is preemptible to it, and implements context switching, after another process is scheduled, the process is in the preemptive status), created (the process has just been created and exists, but it is neither ready nor sleep, this state is the initial state of all processes except process 0) and the dead state (the process stops calling exit and the process no longer exists, but it is still recorded in the progress table, this record can be collected by the parent process ).
Next, we will explain the "life and death" of the state transition of a Linux process from creation to extinction ".
(1) the process is created by the parent process through the system call fork and is in the creation state;
(2) after the fork call configures the kernel data structure and the private data structure of the sub-process for the sub-process, the sub-process enters the ready state (or is ready in the memory, or the SWAP device is ready because the memory is not enough );
(3) If the process is ready in the memory, the process can be scheduled by the kernel scheduler to run on the CPU;
(4) the kernel schedules the process to enter the kernel state, and then returns the kernel state to the user state for execution. After a certain period of time, the process is scheduled by the scheduling program and enters the kernel state, and then enters the ready state. Sometimes, when a process is running in the user state, it also enters the kernel state by using the system call because the kernel service is required. after the service is completed, the kernel state is changed back to the user state. Note that a process may be preemptible when returning from the kernel status to the user status. this is because a process with a higher priority needs to use the CPU urgently and cannot wait until the next scheduling time, this leads to preemption;
(5) The process executes the exit call and enters the frozen state, and ends.
2. Process control
Process control mainly involves process creation, sleep, and exit. in Linux, the fork, exec, and clone process creation methods are provided, sleep process sleep and exit process exit call. In addition, Linux also provides the system call wait for the parent process to wait for the child process to end.
Fork
For those who have never been familiar with the Unix/Linux operating system, fork is one of the most difficult concepts to understand. it returns two values after one execution, which is completely "incredible ". First look at the following program
:
Int main ()
{
Int I;
If (fork () = 0)
{
For (I = 1; I <3; I ++)
Printf ("This is child process" n ");
}
Else
{
For (I = 1; I <3; I ++)
Printf ("This is parent process" n ");
}
}
The execution result is:
This is child process
This is child process
This is parent process
This is parent process
In English, fork means "fork". This name is very vivid. A process is running. if fork is used, another process is generated, and the process is "forked ".
. The current process is a parent process, and a child process is generated through fork. For the parent process, the fork function returns the process number of the subroutine, and for the subroutine, the fork function returns zero, which is the essence of a function that returns twice. It can be said that the fork function is one of the most outstanding achievements of Unix systems. it is a long-term hard exploration of Unix developers in the early 1970s S in theory and practice.
Achievements made by Suo.
If we put the loop in the above program a little bigger:
Int main ()
{
Int I;
If (fork () = 0)
{
For (I = 1; I <10000; I ++)
Printf ("This is child process" n ");
}
Else
{
For (I = 1; I <10000; I ++)
Printf ("This is parent process" n ");
}
};
Then we can see the concurrent execution of the parent and child processes, and output "This is child process" and "This is parent process" in turn ".
At this moment, we have not fully understood the fork () function. let's look at the following program to see how many processes are generated and what program output is?
Int main ()
{
Int I;
For (I = 0; I <2; I ++)
{
If (fork () = 0)
{
Printf ("This is child process" n ");
}
Else
{
Printf ("This is parent process" n ");
}
}
};
Exec
In Linux, you can use the exec function family, which contains multiple functions (execl, execlp, execle, execv, execve, and execvp) to start a process with a specified path and file name.
The features of the exec function family are as follows: Once a process calls the exec function, the program being executed is killed. The system replaces the code segment with a new program (executed by the exec function) and the original data segment and stack segment are also discarded. The new data segment and stack segment are allocated, but the process number is retained. That is to say, the exec execution result is: the system considers that the execution is still the original process, but the program corresponding to the process is replaced.
The fork function can create a sub-process and the current process does not die. if we call the exec function family in the fork sub-process, we can execute the code of the parent process and start a new one.
It is really amazing to specify a process. The combination of fork and exec cleverly solves the problem that the program starts the execution of another program but continues to run. See the following example:
Char command [MAX_CMD_LEN];
Void main ()
{
Int rtn;/* the return value of the sub-process */
While (1)
{
/* Read the command to be executed from the terminal */
Printf ("> ");
Fgets (command, MAX_CMD_LEN, stdin );
Command [strlen (command)-1] = 0;
If (fork () = 0)
{
/* The sub-process executes this command */
Execlp (command, command );
/* If the exec function returns, the command is not executed normally and the error message is printed */
Perror (command );
Exit (errorno );
}
Else
{
/* Parent process. wait until the child process ends and print the return value of the child process */
Wait (& rtn );
Printf ("child process return % d" n ", rtn );
}
}
};
This function basically implements a shell function. it reads the process name and parameters entered by the user and starts the corresponding process.
Clone
Clone is a new function provided after Linux2.0. it is more powerful than fork (you can think that fork is a part of clone implementation), so that the created sub-process can share the resources of the parent process and
To use this function, you must set the clone_actually_works_ OK option when compiling the kernel.
The clone function is prototype:
Int clone (int (* fn) (void *), void * child_stack, int flags, void * arg );
This function returns the PID of the created process. The flags flag in the function is used to set related options when creating a sub-process. The meanings are as follows:
Flag
Description
CLONE_PARENT
The parent process of the created child process is the caller's parent process. The new process and the process that created the child process become "brother" rather than "parent and child"
CLONE_FS
The sub-process shares the same file system with the parent process, including root, current directory, and umask.
CLONE_FILES
The sub-process shares the same file descriptor with the parent process.
CLONE_NEWNS
In the new namespace promoter process, namespace describes the process File hierarchy
CLONE_SIGHAND
The sub-process shares the same signal processing (signal handler) table with the parent process.
CLONE_PTRACE
If the parent process is traced, the child process is also traced.
CLONE_VFORK
The parent process is suspended until the child process releases virtual memory resources.
CLONE_VM
The sub-process and parent process run in the same memory space
CLONE_PID
The PID is consistent with the parent process when the child process is created.
CLONE_THREAD
Added to support POSIX Thread standards in Linux 2.4. the sub-process shares the same thread group with the parent process.
Let's look at the following example:
Int variable, fd;
Int do_something (){
Variable = 42;
Close (fd );
_ Exit (0 );
}
Int main (int argc, char * argv []) {
Void ** child_stack;
Char tempch;
Variable = 9;
Fd = open ("test. file", O_RDONLY );
Child_stack = (void **) malloc (16384 );
Printf ("The variable was % d" n ", variable );
Clone (do_something, child_stack, CLONE_VM | CLONE_FILES, NULL );
Sleep (1);/* delay so that the sub-process can close the file and modify the variable */
Printf ("The variable is now % d" n ", variable );
If (read (fd, & tempch, 1) <1 ){
Perror ("File Read Error ");
Exit (1 );
}
Printf ("We cocould read from the file" n ");
Return 0;
}
Running output:
The variable is now 42
File Read Error
The output result of the program tells us that the sub-process closes the file and modifies the variable (the CLONE_VM and CLONE_FILES flags used for calling clone will make the variables and file descriptor tables
), The parent process will immediately feel it, which is the characteristic of clone.
Sleep
The function call sleep can be used to suspend a process for a specified number of seconds. the prototype of this function is:
Unsigned int sleep (unsigned int seconds );
This function call causes the process to suspend for a specified time. if the specified suspension time is reached, the call returns 0. if the function call is interrupted by the signal, the remaining suspension time is returned.
(The specified time minus the suspended time ).
Exit
The system calls the exit function to terminate the process. its function prototype is:
Void _ exit (int status );
_ Exit will immediately terminate the calling process, and all file descriptors belonging to the process will be disabled. The status parameter is returned to the parent process as the exit status value.
Wait can obtain this value.
Wait
Wait system calls include:
Pid_t wait (int * status );
Pid_t waitpid (pid_t pid, int * status, int options );
The purpose of wait is to ensure that any sub-process that sends a call will sleep until one of them stops. the waitpid waits for the sub-process specified by the pid parameter to exit.
3. Inter-process communication
In Linux, inter-process Communication (IPC) includes pipelines, message queues, shared memory, semaphores, and APIs.
Pipelines can be divided into famous and unknown pipelines. nameless pipelines can only be used for communication between relatives' processes, while famous pipelines can be used between non-kinship processes.
# Define INPUT 0
# Define OUTPUT 1
Void main ()
{
Int file_descriptors [2];
/* Define the sub-process number */
Pid_t pid;
Char buf [BUFFER_LEN];
Int returned_count;
/* Create an unknown MPs queue */
Pipe (file_descriptors );
/* Create a sub-process */
If (pid = fork () =-1)
{
Printf ("Error in fork" n ");
Exit (1 );
}
/* Execute the sub-process */
If (pid = 0)
{
Printf ("in the spawned (child) process..." n ");
/* The sub-process writes data to the parent process and closes the read end of the MPs queue */
Close (file_descriptors [INPUT]);
Write (file_descriptors [OUTPUT], "test data", strlen ("test data "));
Exit (0 );
}
Else
{
/* Execute the parent process */
Printf ("in the spawning (parent) process..." n ");
/* The parent process reads data written by the sub-process from the MPs queue and closes the data written by the MPs queue */
Close (file_descriptors [OUTPUT]);
Returned_count = read (file_descriptors [INPUT], buf, sizeof (buf ));
Printf ("% d bytes of data converted Ed from spawned process: % s" n ",
Returned_count, buf );
}
}
In the above procedures
Int pipe (int filedis [2]);
Method Definition. the filedis parameter returns two file descriptors, filedes [0], to open for Read, and filedes [1] to open for write, output of filedes [1] is the input of filedes [0;
In Linux, a famous pipeline can be created in either of the following ways ):
(1) mkfifo ("kerberoexample", "rw ");
(2) mknod implements oexample p
Mkfifo is a function, and mknod is a system call, that is, we can output the above Command in shell.
After a famous pipeline is created, we can read and write it like a read/write file:
/* Process 1: read a famous pipeline */
Void main ()
{
FILE * in_file;
Int count = 1;
Char buf [BUFFER_LEN];
In_file = fopen ("pipeexample", "r ");
If (in_file = NULL)
{
Printf ("Error in fdopen." n ");
Exit (1 );
}
While (count = fread (buf, 1, BUFFER_LEN, in_file)> 0)
Printf ("received from pipe: % s" n ", buf );
Fclose (in_file );
}
/* Process 2: write a famous pipeline */
Void main ()
{
FILE * out_file;
Int count = 1;
Char buf [BUFFER_LEN];
Out_file = fopen ("pipeexample", "w ");
If (out_file = NULL)
{
Printf ("Error opening pipe .");
Exit (1 );
}
Sprintf (buf, "this is test data for the named pipe example" n ");
Fwrite (buf, 1, BUFFER_LEN, out_file );
Fclose (out_file );
}
Message queues are used for communication between processes running on the same machine, similar to pipelines;
The shared memory is usually created by a process, and other processes read and write the memory area. There are two ways to get the shared memory: ing/dev/mem device and memory image file. Previous method
It does not bring additional overhead to the system, but is not commonly used in reality, because it controls the access to the actual physical memory. the common method is to achieve shared memory through the shmXXX function family:
Int shmget (key_t key, int size, int flag);/* get a shared storage identifier */
This function enables the system to allocate the size of memory for shared memory;
Void * shmat (int shmid, void * addr, int flag);/* connect the shared memory to its own address space */
Shmid is the shared storage identifier returned by the shmget function. the addr and flag parameters determine how to determine the connection address. The return value of the function is the instance connected to the data segment of the process.
Address. Then, the process can read and write the address to access the shared memory.
Essentially, semaphores are a counter used to record access to a resource (such as shared memory. Generally, to obtain shared resources, the process must perform the following operations:
(1) testing the semaphore that controls the resource;
(2) If the semaphore value is positive, the resource can be used, and the process will reduce the number of incoming signs by 1;
(3) If the semaphore is 0, the resource is currently unavailable, and the process enters the sleep state until the signal value is greater than 0, and the process is awakened. transfer to step (1 );
(4) when a process no longer uses a semaphore-controlled resource, the signal value is incremented by 1. if a process is sleeping and waiting for this semaphore, it is awakened.
The following is an example of using semaphores. this program creates a keyword for a specific IPC structure and a semaphores, creates an index for this semaphores, modifies the semaphore value pointed to by the index, and
Then clear the semaphore:
# Include
# Include
# Include
# Include
Void main ()
{
Key_t unique_key;/* defines an IPC keyword */
Int id;
Struct sembuf lock_it;
Union semun options;
Int I;
Unique_key = ftok (".", 'A');/* generate a keyword. The character 'A' is a random seed */
/* Create a new semaphore set */
Id = semget (unique_key, 1, IPC_CREAT | IPC_EXCL | 0666 );
Printf ("semaphore id = % d" n ", id );
Options. val = 1;/* Set the variable value */
Semctl (id, 0, SETVAL, options);/* Set the semaphore of index 0 */
/* Print the semaphore value */
I = semctl (id, 0, GETVAL, 0 );
Printf ("value of semaphore at index 0 is % d" n ", I );
/* Reset the Semaphore below */
Lock_it.sem_num = 0;/* specifies the Semaphore */
Lock_it.sem_op =-1;/* define operation */
Lock_it.sem_flg = IPC_NOWAIT;/* operation method */
If (semop (id, & lock_it, 1) =-1)
{
Printf ("can not lock semaphore." n ");
Exit (1 );
}
I = semctl (id, 0, GETVAL, 0 );
Printf ("value of semaphore at index 0 is % d" n ", I );
/* Clear semaphores */
Semctl (id, 0, IPC_RMID, 0 );
}
Socket communication is not proprietary to Linux. almost all operating systems that provide TCP/IP protocol stacks provide sockets. in all such operating systems, socket programming methods
Almost the same.
4. section
This chapter describes the concept of Linux processes and explains process control and inter-process communication methods with multiple instances. understanding this chapter is the key to understanding the Linux operating system.