Linux 进程间通信(二)

信号

对于Linux来说,信号是软中断,许多重要的程序都需要处理信号。信号为Linux提供了一种处理异步事件的方法。比如,终端用户输入了ctrl+c到终端程序,会通过信号机制停止一个程序。信号其实也类似于单片机中的中断。
Linux系统中信号有很多种,它们都有各自的名字和序号,可以用kill -l指令来查看。

 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	

其中,信号名字都是以”SIG”开头,从1开始编号的,不存在0号信号。
信号的处理:
信号的处理有三种方法:分别是:忽略捕捉默认动作
忽略信号,大多信号可以使用这个方式来处理,但是有两种信号不能忽略(分别是SIGKILL和SIGSTOP),因为他们向内核和超级用户提供了进程终止和停止的可靠方法。如果忽略了,那么这个进程就变成了没人能管理的进程,显然是内核设计者不希望看到的场景。
捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,如何将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。

在终端里输入kill命令可以向指定进程发送信号,例如kill -9 PID用来杀死指定进程,可以通过ps -aux查看进程PID。不难发现,序号9对应的信号名为SIGKILL即杀死进程信号,我们也可以通过序号来发生其他信号。

API
一、signal函数
通过signal函数将hanlder函数设置为序号为signum的信号处理函数,注意的是,sighandler_t定义的是参数只有int类型的函数指针类型。
函数原型:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
参数:
signum:信号序号
handler:处理函数指针
返回值:
若成功返回之前的处理函数指针,失败返回SIG_ERR。

二、kill函数
向指定进程发送信号
函数原型:

#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
参数:
pid:进程号
sig:信号序号
返回值:
若成功返回0,失败返回-1并设置errno。

例程:sig.c以捕捉形式处理信号,并根据信号的序号打印信号名字。
mykill.c 模仿kill命令向指定进程发送信号。

//文件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;
}
//文件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]);
        //也可以使用system函数使用kill命令
        /*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;
}

实验结果可以看出向进程发送SIGKILL信号时,进程仍然会被杀死。所以SIGKILL信号无法更改信号处理函数。若要对信号进行忽略,signal函数中的handler参数设置为SIG_IGN即可。

信号可以携带消息,上面我们侧重讲了信号的动作方面,比如你在家里听到陌生人敲门,你只能知道敲门这个动作。如果陌生人同时喊了自己是谁,这时就携带了消息。
信号如何携带消息呢?
Linux也提供了一些API:
一、sigaction函数
功能:检查并更改信号动作
与signal函数相比,sigaction函数更加健壮,可以携带消息。
函数原型:

#include <signal.h>
//参数:signum:信号序号,act:对该信号新配置,oldact:对该信号旧配置进行备份
//返回:成功返回0,失败返回-1。
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
//sigaction 结构体定义
struct sigaction {
//sa_handler和sa_sigaction只能二选一配置
void     (*sa_handler)(int);//信号处理程序,不接受额外数据
void     (*sa_sigaction)(int, siginfo_t *, void *);//信号处理程序,能够接收额外数据。其中,第一个参数为信号序号,第二个参数为 siginfo_t结构体,存储额外数据,第三个参数指针如果为空,表示没有接收到额外数据,如果不为空,表示接收到额外数据
sigset_t   sa_mask;//阻塞关键字的信号集
int        sa_flags;//影响信号的行为,SA_SIGINFO表示能够接收额外数据
void     (*sa_restorer)(void);//系统已废弃使用
};
//siginfo_t结构体定义
siginfo_t {
    int      si_signo;    /* 信号序号 */
    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;      /* 发送信号的进程PID */
    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结构体,用来存储数据 */
    int      si_int;      /*整数型数据*/
    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 联合体定义
union sigval {
	int   sival_int;//整数型数据
	void *sival_ptr;//无类型指针(可以是任何类型的数据指针)
};

二、sigqueue函数
将信号和数据排入进程队列
函数原型:

#include <signal.h>
//参数:
//pid:进程号
//sig:信号序号
//value:sigval 联合体型,信号所携带的数据
//返回:成功返回0,失败返回-1并设置errno
int sigqueue(pid_t pid, int sig, const union sigval value);
//sigval 联合体定义
union sigval {
int   sival_int;//整数型数据
void *sival_ptr;//无类型指针(可以是任何类型的数据指针)
};

例程:信号携带整数型数据

//文件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; 
}
//文件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;
}

接收端命令行:

Ubuntu@Embed_Learn:~/learn/ipc$ ./sigrcv
the process pid:37428
signum:10
data:54
data:54
form pid:37431

发送端命令行:

Ubuntu@Embed_Learn:~/learn/ipc$ ./sigsend 10 37428 54
the process pid:37431

信号量

信号量与已经介绍过的IPC结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间的通信数据。
1、特点
(1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
(2)信号量基于操作系统的PV操作,程序对信号量的操作都是原子操作。
(3)每次对信号量的PV操作不仅限于对信号量加1或减1,而且可以加减任意正整数。
(4)支持信号量组。
2、函数原型

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//创建或获取一个信号量组:若成功返回信号量组ID,失败返回-1
int semget(key_t key, int nsems, int semflg);
//对信号量组进行操作,改变信号量的值,成功返回0,失败返回-1
int semop(int semid, struct sembuf *sops, unsigned nsops);
//控制信号量的相关信息,成功返回0,失败返回-1
int semctl(int semid, int semnum, int cmd, ...);

例程:

#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 联合体定义,用于semctl函数
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操作拿锁:信号量减1操作
{   
        struct sembuf sop;
        sop.sem_num = 0;        /* 信号量编号 */
        sop.sem_op = -1;         /* 信号量减1 */
        sop.sem_flg = SEM_UNDO;/* 设置为阻塞并且当进程结束时,取消互斥锁 */
        if(semop(semid, &sop, 1)==-1)
        {
                printf("sem option fail!\n");
        }
}
void Vputbackkey(int semid)//V操作放回锁:信号量加1操作
{
        struct sembuf sop;
        sop.sem_num = 0;        /* 信号量编号 */
        sop.sem_op = 1;         /* 信号量加1 */
        sop.sem_flg = SEM_UNDO;/* 设置为阻塞并且当进程结束时,取消互斥锁 */
        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)//创建key值
        {
                printf("key  create fail!\n");
        }
        semid=semget(key,1,IPC_CREAT|0666);//创建信号量组并获取信号量组ID,信号量组设置为只有1个信号量
        set.val=0;//初值为0
        semctl(semid,0,SETVAL,set);//设置第一个信号量初值为0,第二个参数为序号
        pid=fork();//创建子进程
        if(pid<0)
        {
                printf("process create fail!\n");
        }
        else if(pid==0)
        {
                printf("this is son process!\n");
                Vputbackkey(semid);//V操作:放回锁
        }
        else
        {
                Pgetkey(semid);//P操作:拿锁
                printf("this is father process!\n");
                Vputbackkey(semid);//V操作:放回锁
				semctl(semid,0,IPC_RMID);//移除信号量组,第二个参数可以忽略
        }
        return 0;
}

实验结果:

Ubuntu@Embed_Learn:~/learn/ipc$ ./sem
this is son process!
this is father process!

在父进程中,P操作拿锁:信号量减1操作,此时信号量值为0,要当信号量大于等于1时,才能执行完毕。不然会发生阻塞,直到信号量大于等于1。所以,先运行子进程中的printf函数输出字符串"this is son process!\n",然后进行V操作放回锁:信号量加1操作,此时信号量等于1,父进程才执行printf函数输出字符串"this is father process!\n"。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一盆电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值