Linux interprocess communication

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".

Tags: C Linux stm32

Posted by ali_mac1 on Tue, 21 Sep 2021 01:21:05 +0530