Inter-thread Communication, fork (), waitpid (), signal, capture signal, execute shell command with C, shared memory, mmap
Experimental requirements:
1. Simple shell: Implement basic command-line shell operation through C, implement two functions, main () and setup ().
Setup reads the user's next instruction (up to 80 characters), then breaks it down into separate tags and executes, after the user presses CTRL+D, the program terminates.
The main function prints the prompt command->, waits for the user to enter the command, if the user command ends with "&", then executes concurrently, otherwise the parent process waits for the child process
2. Create historical features: The command number, allows the user to access the last 10 input commands, when the user presses CTRL + C, the system lists these commands, the user input "R x" can run the command, if x exists, the output executes the most recent X-prefixed command, otherwise, The output executes the most recent command. If the command you are about to execute is an error command, output the user prompt and do not add the command to the history cache.
One. Handling ctrl+c,ctrl+d signals: Use signal here ()
Signal () transferred from Baidu Encyclopedia
Table header File#include <signal.h>
Features:Set the corresponding action of a signal
function Prototypes: void (*signal (int signum,void (* handler))) (int), or: typedef void (*sig_t) (int); sig_t signal (int signum,sig_t handler);
parameter Description: The first parameter, Signum, indicates the type of signal to be processed, and it can take any signal except Sigkill and sigstop. The second parameter, handler, describes the action associated with the signal, which can take the following three values: (1) A function address with no return value This function must be declared before signal () is called, Handler is the name of the function. When a signal of type Signum is received, the function specified by handler is executed. This function should be defined in the following form: void func (int sig), (2) Sig_ign This symbol means that the signal is ignored, and after the corresponding signal () call is executed, the process ignores the signal of type sig. (3) SIG_DFL This symbol indicates the default processing of the recovery system to the signal.
Second: Execute shell command with C, There are 3 ways to do this:
1.system (), Inheritance environment variable, unsafe
2.popen () Establishing pipelines, inheriting environment variables, unsafe
3.exec family Function +fork ()
Use exec+fork here
There are 6 exec family functions, the function is to execute the corresponding file, and enter additional parameters, if the execution succeeds directly end, if unsuccessful will return 1, then execute the following code, the error message is saved in errno
This time use is EXECVP,int execvp (const char *file, char *const argv[]);
Where file is passed into the shell command, argv is a C string array, the array argv[0] is file,arg[1]....arg[n] is a split command argument, arg[n+1] is null pointer null, (n is the number of command arguments), such as "Ls-l", The file= "LS" should be passed in, argv[3]={"LS", "-L", NULL}
exec function Family: reprinted from: http://blog.csdn.net/aile770339804/article/details/7443921
#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[]);
The EXEC function family loads and runs the program pathname and passes the parameter arg0 (arg1,arg2,argv[],envp[]) to the subroutine, with an error of return-1. In the EXEC function family, after the suffix L, V, p, E is added to exec, the function specified will have some operational capability with a suffix:
Only EXECVE is the real system call, and the others are packaged library functions on this basis.
In fact, we take a look at these 6 functions, we can find that the first 3 functions are all beginning with execl, and the last 3 are execv.
First of all, compare the first two functions Execv and execl. The function at the beginning of EXECV is to pass command-line arguments in the form of "char *argv[]". The function that begins with EXECL takes the way that we are more accustomed to, putting the parameters in one column and ending with a null representation, or (char *) 0.
Next followed by the 2 functions EXECLP and EXECVP with P endings. Compared to several other functions, 4 functions except EXECLP and EXECVP require that their 1th parameter path must be a complete path, such as "/bin/ls", while the 1th parameter of EXECLP and EXECVP file can be simply a file name, such as " LS ", these two functions can be automatically found in the directory where the environment variable path is set.
The last two functions, execle and EXECVE, use char *envp[] to pass the environment variable. Of all 6 functions, only execle and EXECVE need to pass environment variables, and none of the other 4 functions have this parameter, which does not mean that they do not pass environment variables, and these 4 functions will pass the default environment variable to the executed application without any modification. Execle and EXECVE will replace the default ones with the specified environment variables.
Finally, we should emphasize that in the normal programming, if you use the EXEC function family, you must remember to add the wrong judgment statement. Because exec is prone to injury compared to other system calls, the location of files being executed, permissions, and many other factors can cause the call to fail. The most common errors are:
1. Cannot find the file or path, at this time errno is set to enoent;
2. Array argv and envp forget to end with NULL, at this time errno is set to Efault;
3. There is no run permission on the file to be executed, at which point the errno is set to Eacces.
Because if successful, EXECVP will exit directly, so EXECVP should run in the child process generated by the fork.
Fork start knowledge reproduced from http://blog.csdn.net/jason314/article/details/5640969
a process, including code, data, and resources assigned to the process. The fork () function creates a process that is almost identical to the original process through a system call, that is, two processes can do exactly the same thing, but two processes can do different things if the initial parameters or the variables passed in are different.
After a process calls the fork () function, the system first assigns resources to the new process, such as space for storing data and code. All the values of the original process are then copied to the new new process, with only a few values that are different from the value of the original process. The equivalent of cloning a self.
Let's look at an example:
[CPP]View Plaincopy
- /*
- * FORK_TEST.C
- * Version 1
- * Created on:2010-5-29
- * Author:wangth
- */
- #include <unistd.h>
- #include <stdio.h>
- int main ()
- {
- pid_t Fpid; //fpid represents the value returned by the fork function
- int count=0;
- Fpid=fork ();
- if (Fpid < 0)
- printf ("error in fork!");
- Else if (fpid = = 0) {
- printf ("I am the child process, my process ID is%d/n", getpid ());
- printf ("I am father's son/n"); For some people, Chinese looks more straightforward.
- count++;
- }
- else {
- printf ("I am the parent process, my process ID is%d/n", getpid ());
- printf ("I am the child his father/n");
- count++;
- }
- printf ("statistic result:%d/n", count);
- return 0;
- }
Running results are:
i am the child process, my process ID is 5574
I'm dad. Son
statistics are: 1
I am the parent process, my process ID is 5573
&nbs P I'm the kid, his dad.
statistics are: 1
before statement fpid=fork (), only one process executes this code, but after this statement, it becomes two processes executing, The two processes are almost identical, and the next statement that will be executed is the IF (fpid<0) ...
Why the fpid of two processes is different, which is related to the characteristics of the fork function. fork call is that it is only called once, but it can return two times, it may have three different return values:
1) In the parent process, Fork returns the process ID of the newly created child process,
2) in the child process, fork returns 0;
3) If an error occurs, fork returns a negative value;
After the fork function finishes executing, if the new process is created successfully, there are two processes, one child process and one parent process. In the subprocess, the fork function returns 0, and in the parent process, fork returns the process ID of the newly created child process. We can determine whether the current process is a child process or a parent process by the value returned by the fork.
Quote A netizen to explain why the value of Fpid is different in the parent-child process. "In fact, the equivalent of a linked list, the process forms a linked list, the parent process fpid (p means point) to the process ID of the child process, because the child process has no child process, so its fpid is 0.
There are two possible reasons for fork errors:
1) The current number of processes has reached the system-specified limit, when the value of errno is set to Eagain.
2) system memory is low, then the value of errno is set to Enomem.
After a successful creation of a new process, there are two fundamentally identical processes in the system, which do not have a fixed sequencing and which process first executes the process scheduling policy to look at the system.
Each process has a unique (distinct) process ID, which can be obtained through the getpid () function, a variable that records the PID of the parent process, and the value of the variable can be obtained through the getppid () function.
after the fork executes, two processes appear,
Some people say that the content of the two process is exactly the same Ah, how to print the result is not the same ah, that is because the reason for judging the conditions, the above list is only the process of code and instructions, and variables ah.
After the fork is executed, the variable for process 1 is count=0,fpid! =0 (parent process). The variable for process 2 is count=0,fpid=0 (child process), the variables of both processes are independent, there are different addresses, not shared, and this should be noted. We can say that we are using fpid to identify and manipulate parent-child processes.
Others may wonder why the code is not copied from # include, because fork is a copy of the current situation of the process, and when the fork is executed, the process has finished executing int count=0;fork only the next code to execute to the new process.
Three. Inter-process communication
The program needs to record whether the execution of the command is successful, and the result of the exec is within the sub-process of the fork, so use mmap to map the same file for shared memory
Mmap () and related system calls--reprinted from http://fengtong.iteye.com/blog/457090
Mmap () system calls enable shared memory between processes by mapping the same common file. After the normal file is mapped to the process address space, the process can access the same file as the normal memory without having to call read (), write (), and so on.
Note: In fact, the mmap () system call is not designed entirely for shared memory. It itself provides a different way of accessing ordinary files than normal, and processes can operate on ordinary files like read-write memory. The shared memory IPC for POSIX or System V is purely for sharing purposes, and of course mmap () realizes shared memory is also one of its main applications.
1, mmap () system call form as follows:
void* mmap (void * addr, size_t len, int prot, int flags, int fd, off_t offset)
The parameter fd is the file descriptor that will be mapped to the process space, usually returned by open (), and the FD can be specified as-1, at which point the Map_anon in the flags parameter must be specified, indicating that the anonymous mapping is done (not involving the specific file name, avoiding the creation and opening of the file, Obviously, it can only be used for inter-process communication with affinity. Len is the number of bytes mapped to the calling process address space, starting at offset bytes at the beginning of the mapped file. The prot parameter specifies the access rights for shared memory. The following values may be desirable or: prot_read (readable), prot_write (writable), prot_exec (executable), Prot_none (inaccessible). Flags are specified by the following constant values: map_shared, Map_private, map_fixed, where map_shared, map_private is required, and map_fixed is not recommended. The offset parameter is typically set to 0, which indicates that the mapping starts from the file header. The addr parameter specifies that the file should be mapped to the start address of the process space and is typically assigned a null pointer, at which point the task of selecting the start address is left to the kernel. The return value of the function is the address that the last file maps to the process space, and the process can manipulate the starting address directly to the valid address of the value. The parameters of mmap () are no longer detailed here, and readers can refer to the mmap () manual page for further information.
2. The system calls Mmap () to share memory in two ways:
(1) Memory mapping provided with normal files: applies to any process; At this point, you need to open or create a file and then call Mmap (); The typical calling code is as follows:
Fd=open (name, flag, mode); if (fd<0) ... |
Ptr=mmap (NULL, Len, prot_read| Prot_write, map_shared, FD, 0); There are many features and areas to be aware of in the way that shared memory is communicated through MMAP (), which we will specify in the example.
(2) Use special files to provide anonymous memory mapping: Applies to relationships between processes; Because of the special affinity of a parent-child process, call Mmap () first in the parent process and then call Fork (). Then, after calling Fork (), the child process inherits the address space of the parent process after the anonymous mapping and also inherits the address returned by Mmap (), so that the parent-child process can communicate through the mapped area. Note that this is not a general inheritance relationship. In general, child processes maintain separate variables inherited from the parent process. The address returned by the mmap () is maintained by the parent-child process together.
The best way to implement shared memory for a genetically-related process should be to use anonymous memory mapping. At this point, you do not have to specify a specific file, just set the appropriate flags, see Example 2.
3. System call Munmap ()
int Munmap (void * addr, size_t len)
The call unlocks a mapping relationship in the process address space, addr is the address returned when Mmap () is called, and Len is the size of the map area. When the mapping relationship is lifted, access to the original mapped address causes a segment error to occur.
Program code:
#include <signal.h> #include <sys/mman.h> #include <sys/types.h> #include <unistd.h> #include <cstdio> #include <cstring> #include <cstdlib> #include <iostream> #include <sys/wait.h> #include <queue>using namespace std; #define Buffer_size 50#define MAXLINE 80char buffer[buffer_size];struct mes{/ /used to save the parameters of the command, the number of arguments, whether to execute concurrently, whether the error after execution char** args;//parameter, arg[0] for the command itself int len;//the number of arguments bool bg;//whether the concurrency int erno;//after execution is incorrect Mes (): args (NULL), Len (0), BG (false) {} mes (char **_args,int _len,bool _bg): args (_args), Len (_len), BG (_BG) {} void S How () {//Print command if (erno==-1) cout<< "ERROR:"; for (int i=0;i<len;i++) cout<<args[i]<< ""; if (BG) cout<< "&" <<endl; else cout<<endl; } ~mes () {for (int i=0;i<len&&args[i];i++) {delete[] args[i]; Args[i]=null; } delete[] args; Args=null; }};queue <mes*> Rec,quetmp;//rec for storageIn the last 10 commands, quetmp only temporarily stores the variable number in rec when traversing rec, static int quelen=0;//rec variable count static int reclen=0;//executed command total number of static int srclen=0;// String length has been read bool Read2 (char * des,char *src,bool init) {//Read non-empty string des from string src, failed to return false, read from the same SRC string should be read at once, Init is true when the new SRC string is re-read if (init) srclen=0; BOOL Fl=false; for (; src[srclen];srclen++) {if (src[srclen]!= ' &&src[srclen]!= ' \ t ' &&src[srclen]!= ' \ n ') { Fl=true; for (int i=0;src[srclen];srclen++,i++) {if (src[srclen]== ' | | | src[srclen]== ' \ t ' | | src[srclen]== ' \ n ') break; Des[i]=src[srclen]; des[i+1]=0; } break; }} return FL;} void Setup (char inputbuffer[],char*args[],bool background) {//execute command Inputbuffer–args[1]–arg[2]...,background to true represents concurrency int len=0; for (len=0;len<maxline/2+1&&args[len]!=null;len++) {}//re-creates the args array to ensure that the data form satisfies EXECVP char **_args=new char* [Len+1]; for (int i=0;i<len;i++) {_args[i]=new Char[strlen (args[i]) +1]; strcpy (_args[i],args[i]); } _args[len]=null; mes* pmes=new mes (_args,len,background); int * p_errno= (int*) mmap (null,sizeof (int) *2,prot_read| Prot_write, map_shared| map_anonymous,-1,0);//share memory in order to pass the execution result of the child process int pid=fork (); if (pid==0) {int ERNO=EXECVP (Inputbuffer,_args); *p_errno=erno; _exit (0);//Sub-process quits in time} else{if (!background) {waitpid (pid,null,0); }} pmes->erno=*p_errno; if (quelen<10) {Rec.push (pmes); quelen++; } else {delete Rec.front (); Rec.pop (); Rec.push (PMEs); } reclen++;} void Handle_sigint (int sig) {//History cache tool int ci=reclen-quelen,ind=0; Mes* LS[10]; cout<<endl; while (!rec.empty ()) {cout<<++ci<< ":"; Rec.front ()->show (); Quetmp.push (Rec.front ()); Ls[ind++]=rec.front (); Rec.pop (); }
while (!quetmp.empty ()) {
Rec.push (Quetmp.front ()); Quetmp.pop ();
} cout<< "Exit record input \" Q\ ", repeat command,input \" r\ "or \" R x\ "(x is the prefix of command)" <<end L Char Buff[maxline],buff2[maxline]; int exenum=ind-1; while (true) {fgets (Buff,maxline,stdin); Read2 (buff2,buff,true); if (strcmp (BUFF2, "Q") ==0) break; if (strcmp (BUFF2, "R")!=0) {cout<< "No such command" <<endl; Continue } if (Read2 (Buff2,buff,false)) {for (; exenum>=0;exenum--) {bool fl=true; for (int i=0;buff2[i]!=0;i++) {if (Buff2[i]!=ls[exenum]->args[0][i]) {Fl=false ; Break }} if (fl) break; } if (exenum<0) cout<< "No such prefix" <<endl; } if (exenum>=0) {ls[exenum]->show (); if (ls[exenum]->erno==-1) {cout<< "It is an error command!"<<endl; } else {setup (LS[EXENUM]->ARGS[0],LS[EXENUM]->ARGS,LS[EXENUM]->BG); }}} cout<< "Record has quitted" <<ENDL;} void handle_sigtstp (int sig) {//ctrl+d exit write (Stdout_fileno,buffer,strlen (buffer)); Exit (0);} int main (int argc,char * arg[]) {char inputbuffer[maxline],buff[maxline]; BOOL background; char * args[maxline/2+1]; memset (args,0,sizeof (args)); Signal (Sigint,handle_sigint); Signal (SIGTSTP,HANDLE_SIGTSTP); while (true) {background=false; cout<< "command->"; Fgets (Inputbuffer,maxline,stdin);//Read split command int len=0; while (Read2 (buff,inputbuffer,len==0)) {if (Args[len]) delete[] Args[len]; Args[len++]=new Char[strlen (Buff)]; strcpy (Args[len-1],buff); } if (Args[len]) delete[] Args[len]; if (len>0&&args[len-1][0]== ' & ') {delete[] args[len-1]; Args[lEn-1]=null; len--; Background=true; } setup (Args[0],args,background); for (int i=0;i<len;i++) {delete args[i]; Args[i]=null; }} return 0;}
Operating System Concepts with Java project: Shell Unix and historical features