signal
For Linux, signals are soft interrupts, and many important programs need to process signals. Semaphores provide Linux with a way to handle asynchronous events. For example, if the end user inputs ctrl+c to the terminal program, it will stop a program through the signal mechanism. In fact, the signal is also similar to the interrupt in MCU.
There are many kinds of signals in Linux system. They all have their own names and serial numbers, which can be viewed with the kill -l instruction.
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM 16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP 21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ 26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR 31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3 38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8 43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13 48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12 53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7 58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2 63) SIGRTMAX-1 64) SIGRTMAX
Among them, the signal names start with "SIG" and are numbered from 1. There is no signal 0.
Signal processing:
There are three methods for signal processing: ignore, capture and default action.
Ignore signals. Most signals can be processed in this way, but there are two signals that cannot be ignored (SIGKILL and SIGSTOP respectively), because they provide the kernel and super users with reliable methods for process termination and stop. If ignored, the process will become a process that no one can manage, which is obviously a scenario that the kernel designer does not want to see.
To capture a signal, you need to tell the kernel how the user wants to process a signal. To put it bluntly, you need to write a signal processing function and how to tell the kernel about this function. When the signal is generated, the kernel calls the user-defined function to realize some signal processing.
System default action. For each signal, the system corresponds to the default processing action. When the signal occurs, the system will execute it automatically.
Enter the Kill Command in the terminal to send a signal to the specified process. For example, kill -9 PID is used to kill the specified process through ps
-aux view process PID. It is not difficult to find that the signal corresponding to No. 9 is named SIGKILL, that is, the kill process signal. We can also generate other signals through the serial number.
API
1, signal function
Set the hanlder function as the signal processing function with the sequence number of signum through the signal function. Note that sighandler_t defines a function pointer type with only int arguments.
Function prototype:
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
Parameters:
signum: signal serial number
handler: handle function pointers
Return value:
If the previous processing function pointer is returned successfully, SIG is returned if it fails_ ERR.
2, kill function
Sends a signal to the specified process
Function prototype:
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
Parameters:
pid: process number
sig: signal serial number
Return value:
If it succeeds, it returns 0, if it fails, it returns - 1 and sets errno.
Routine: sig.c processes the signal in the form of capture, and prints the signal name according to the serial number of the signal.
mykill.c imitates the kill Command and sends a signal to the specified process.
//File sig.c #include <stdio.h> #include <signal.h> typedef void (*sighandler_t)(int); void handler(int signum) { printf("signum:%d\n",signum); switch(signum) { case 2: printf("this is SIGINT\n"); break; case 9: printf("this is SIGKILL\n"); break; case 10: printf("this is SIGUSR1\n"); break; } printf("never quit!\n"); } int main() { signal(2,SIG_IGN); signal(9,handler); signal(10,handler); while(1); return 0; } //File mykill.c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <signal.h> int main(int argc,char* argv[]) { char cmd[1024]; memset(cmd,'\0',1024); if(argc!=3) { printf("arg error!\n"); exit(-1); } pid_t pid=atoi(argv[2]); int sig=atoi(argv[1]); //You can also use the kill command using the system function /*sprintf(cmd,"kill -%d %d",sig,pid); printf("%s\n",cmd); system(cmd);*/ if(kill(pid,sig)==-1) { printf("sig send fail!\n"); perror("why"); } return 0; }
The experimental results show that when SIGKILL signal is sent to the process, the process will still be killed. Therefore, SIGKILL signal cannot change the signal processing function. To ignore the signal, set the handler parameter in the signal function to SIG_IGN is enough.
Signals can carry messages. Above, we focus on the action of signals. For example, when you hear strangers knocking at the door at home, you can only know the action of knocking at the door. If the stranger calls out who he is at the same time, he carries the message.
How do signals carry messages?
Linux also provides some API s:
1, sigaction function
Function: check and change signal action
Compared with the signal function, the sigaction function is more robust and can carry messages.
Function prototype:
#include <signal.h> //Parameters: signum: signal serial number, act: new configuration of the signal, oldact: backup the old configuration of the signal //Return: 0 for success and - 1 for failure. int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact); //sigaction structure definition struct sigaction { //sa_handler and sa_sigaction can only be configured with one of two options void (*sa_handler)(int);//The signal handler does not accept additional data void (*sa_sigaction)(int, siginfo_t *, void *);//Signal processing program that can receive additional data. The first parameter is the signal serial number and the second parameter is siginfo_t structure, which stores additional data. If the third parameter pointer is empty, it means that no additional data has been received. If it is not empty, it means that additional data has been received sigset_t sa_mask;//Semaphore set of blocking keywords int sa_flags;//Behavior affecting signals, SA_SIGINFO indicates that additional data can be received void (*sa_restorer)(void);//The system has been abandoned }; //siginfo_t structure definition siginfo_t { int si_signo; /* Signal serial number */ int si_errno; /* An errno value */ int si_code; /* Signal code */ int si_trapno; /* Trap number that caused hardware-generated signal (unused on most architectures) */ pid_t si_pid; /* Process PID to send signal */ uid_t si_uid; /* Real user ID of sending process */ int si_status; /* Exit value or signal */ clock_t si_utime; /* User time consumed */ clock_t si_stime; /* System time consumed */ sigval_t si_value; /* sigval Structure used to store data */ int si_int; /*Integer data*/ void *si_ptr; /* POSIX.1b signal */ int si_overrun; /* Timer overrun count; POSIX.1b timers */ int si_timerid; /* Timer ID; POSIX.1b timers */ void *si_addr; /* Memory location which caused fault */ long si_band; /* Band event (was int in glibc 2.3.2 and earlier) */ int si_fd; /* File descriptor */ short si_addr_lsb; /* Least significant bit of address (since kernel 2.6.32) */ } //sigval consortium definition union sigval { int sival_int;//Integer data void *sival_ptr;//Typeless pointer (can be any type of data pointer) };
2, sigqueue function
Queue signals and data into processes
Function prototype:
#include <signal.h> //Parameters: //pid: process number //sig: signal serial number //value: sigval, the data carried by the signal //Return: 0 for success, - 1 for failure, and set errno int sigqueue(pid_t pid, int sig, const union sigval value); //sigval consortium definition union sigval { int sival_int;//Integer data void *sival_ptr;//Typeless pointer (can be any type of data pointer) };
Routine: the signal carries integer data
//File sigsend.c #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <unistd.h> /* int sigqueue(pid_t pid, int sig, const union sigval value); */ int main(int argc,char* argv[]) { if(argc!=4) { printf("argu error!\n"); return -1; } union sigval value; pid_t pid=atoi(argv[2]); int signum=atoi(argv[1]); value.sival_int=atoi(argv[3]); sigqueue(pid,signum,value); printf("the process pid:%d\n",getpid()); return 0; } //File sigrcv.c #include <signal.h> #include <stdio.h> #include <sys/types.h> #include <unistd.h> /*int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);*/ void handler(int signum, siginfo_t * info, void * context) { printf("signum:%d\n",signum); if(context!=NULL) { printf("data:%d\n",info->si_int); printf("data:%d\n",info->si_value.sival_int); printf("form pid:%d\n",info->si_pid); } } int main() { printf("the process pid:%d\n",getpid()); struct sigaction act; act.sa_sigaction=handler; act.sa_flags=SA_SIGINFO; sigaction(10,&act,NULL); while(1); return 0; }
Receiving end command line:
Ubuntu@Embed_Learn:~/learn/ipc$ ./sigrcv the process pid:37428 signum:10 data:54 data:54 form pid:37431
Sender command line:
Ubuntu@Embed_Learn:~/learn/ipc$ ./sigsend 10 37428 54 the process pid:37431
Semaphore
The semaphore is different from the IPC structure already introduced. It is a counter. Semaphores are used to realize mutual exclusion and synchronization between processes, not to store communication data between processes.
1. Characteristics
(1) Semaphores are used for inter process synchronization. To transfer data between processes, they need to be combined with shared memory.
(2) Semaphores are based on the PV operation of the operating system, and the operation of the program on semaphores is atomic operation.
(3) Each PV operation on the semaphore is not limited to adding or subtracting 1 to the semaphore, but can add or subtract any positive integer.
(4) Semaphore groups are supported.
2. Function prototype
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> //Create or obtain a semaphore group: if the semaphore group ID is returned successfully, if it fails, return - 1 int semget(key_t key, int nsems, int semflg); //Operate the semaphore group and change the value of the semaphore. 0 is returned for success and - 1 is returned for failure int semop(int semid, struct sembuf *sops, unsigned nsops); //Information related to the control semaphore. 0 is returned for success and - 1 is returned for failure int semctl(int semid, int semnum, int cmd, ...);
Routine:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <unistd.h> #include <stdio.h> //int semget(key_t key, int nsems, int semflg); //int semctl(int semid, int semnum, int cmd, ...); //int semop(int semid, struct sembuf *sops, unsigned nsops); //semun union definition, used for semctl function union semun { int val; /* Value for SETVAL */ struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */ unsigned short *array; /* Array for GETALL, SETALL */ struct seminfo *__buf; /* Buffer for IPC_INFO (Linux-specific) */ }; void Pgetkey(int semid)//P operation lock taking: semaphore minus 1 operation { struct sembuf sop; sop.sem_num = 0; /* Semaphore number */ sop.sem_op = -1; /* Semaphore minus 1 */ sop.sem_flg = SEM_UNDO;/* Set to block and cancel the mutex when the process ends */ if(semop(semid, &sop, 1)==-1) { printf("sem option fail!\n"); } } void Vputbackkey(int semid)//V operation return lock: semaphore plus 1 operation { struct sembuf sop; sop.sem_num = 0; /* Semaphore number */ sop.sem_op = 1; /* Semaphore plus 1 */ sop.sem_flg = SEM_UNDO;/* Set to block and cancel the mutex when the process ends */ if(semop(semid, &sop, 1)==-1) { printf("sem option fail!\n"); } } int main() { pid_t pid; key_t key; int semid; union semun set; if((key=ftok(".",'z'))==-1)//Create key value { printf("key create fail!\n"); } semid=semget(key,1,IPC_CREAT|0666);//Create a semaphore group and obtain the semaphore group ID. the semaphore group is set to have only 1 semaphore set.val=0;//The initial value is 0 semctl(semid,0,SETVAL,set);//Set the initial value of the first semaphore to 0 and the second parameter to serial number pid=fork();//Create child process if(pid<0) { printf("process create fail!\n"); } else if(pid==0) { printf("this is son process!\n"); Vputbackkey(semid);//V operation: put back the lock } else { Pgetkey(semid);//P operation: take the lock printf("this is father process!\n"); Vputbackkey(semid);//V operation: put back the lock semctl(semid,0,IPC_RMID);//Remove the semaphore group, and the second parameter can be ignored } return 0; }
Experimental results:
Ubuntu@Embed_Learn:~/learn/ipc$ ./sem this is son process! this is father process!
In the parent process, the P operation takes the lock: the semaphore minus 1 operation. At this time, the semaphore value is 0. The execution can be completed only when the semaphore is greater than or equal to 1. Otherwise, blocking will occur until the semaphore is greater than or equal to 1. Therefore, first run the printf function in the child process to output the string "this is son process!\n", and then perform the V operation to put the lock back: the semaphore plus 1. At this time, the semaphore is equal to 1, and the parent process executes the printf function to output the string "this is father process!\n".