Linux 进程间通信 —— 信号与信号灯

目录

1. 信号

1.1 信号的概述

1.2 信号的发送(发送信号进程)

1.3 信号的接收

1.4 信号的处理

1.5 利用信号实现进程间通信

1.5.1 signal.c

1.5.2 程序运行结果

2. 信号灯

2.1 信号灯的概述

2.2 相关函数

2.3 信号灯 PV 操作

2.4 代码示例


1. 信号

1.1 信号的概述

信号通信,其实就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。

内核可以发送多少种信号呢?

终端输入 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

信号通信的框架

  • 信号的发送(发送信号进程):kill、raise、alarm
  • 信号的接收(接收信号进程) : pause()、 sleep、 while(1)   // 进程要存在才能接受信号
  • 信号的处理(接收信号进程) :signal

1.2 信号的发送(发送信号进程)

kill:

#include<signal.h>
#include<sys/types.h>
函数原型:int kill(pid_t pid, int sig);

参数:
        函数传入值:pid
                   正数:要接收信号的进程的进程号
                      0:信号被发送到所有和pid进程在同一个进程组的进程
                     ‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外)
                    sig:信号
函数返回值:成功  0  出错  ‐1 

raise: 发信号给自己 == kill(getpid(), sig)

#include<signal.h>
#include<sys/types.h>
函数原型:
int  raise(int sig);

参数:
              函数传入值:sig:信号
函数返回值:
              成功  0  出错  ‐1

alarm : 发送闹钟信号的函数

alarm 与 raise 函数的比较:
相同点:让内核发送信号给当前进程
不同点:

  • alarm 只会发送 SIGALARM 信号
  • alarm 会让内核定时一段时间之后发送信号, raise 会让内核立刻发信号
#include  <unistd.h>
函数原型 unsigned int  alarm(unsigned int seconds)

参数:
         seconds:指定秒数
返回值:
        成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,
否则返回0。    
        出错:‐1
信号名含义默认操作
SIGHUP该信号在用户终端连接(正常或非正常)结束时发出,通常是在终端的控制进程结束时,通知同一会话内的各个作业与控制终端不再关联。终止
SIGINT该信号在用户键入INTR字符(通常是Ctrl-C)时发出,终端驱动程序发送此信号并送到前台进程中的每一个进程。终止
SIGQUIT该信号和SIGINT类似,但由QUIT字符(通常是Ctrl-)来控制。终止
SIGKILL该信号在一个进程企图执行一条非法指令时(可执行文件本身出现错误,或者试图执行数据段、堆栈溢出时)发出。终止
SIGFPE该信号在发生致命的算术运算错误时发出。这里不仅包括浮点运算错误,还包括溢出及除数为0等其它所有的算术的错误。终止
SIGKILL该信号用来立即结束程序的运行,并且不能被阻塞、处理和忽略。终止
SIGALRM该信号当一个定时器到时的时候发出。终止
SIGSTOP该信号用于暂停一个进程,且不能被阻塞、处理或忽略。

暂停

SIGTSTP该信号用于暂停交互进程,用户可键入SUSP字符(通常是Ctrl-Z)发出这个信号。暂停
SIGCHLD子进程改变状态时,父进程会收到这个信号忽略
SIGABORT该信号用于结束进程终止

1.3 信号的接收

要想使接收的进程能收到信号,这个进程不能结束。

sleep
pause:进程状态为 S

函数原型 int  pause(void);

函数返回值  成功:0,出错:‐1

1.4 信号的处理

收到信号的进程,应该怎样处理? 处理的方式:

  • 1.进程的默认处理方式(内核为用户进程设置的默认处理方式)A:忽略 B:终止进程 C: 暂停
  • 2.自己的处理方式:自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的的处理方式
头文件    #include  <signal.h>  
函数原型  void (*signal(int signum, void (*handler)(int)))(int);  

参数  
           signum:指定信号   
           handler 
                   SIG_IGN:忽略该信号。
                   SIG_DFL:采用系统默认方式处理信号
                   自定义的信号处理函数指针  
函数返回值  
           成功:设置之前的信号处理方式    
           出错:‐1
signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自
己写的处理函数;这个函数的返回值是一个函数指针。

1.5 利用信号实现进程间通信

1.5.1 signal.c

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

void sig_usr1(int signum) // 10号信号处理函数
{
	int i = 0;
	while (i < 5)
	{
		i++;
		printf("receive signum = %d, i = %d\n", signum, i);
		sleep(1);
	}
}

void sig_chld(int signum) // 17号信号处理函数
{
	printf("receive signum = %d \n", signum);
	printf("回收子进程\n");
	wait(NULL); // 回收子进程,防止其变成僵尸进程
}

int main()
{
	pid_t pid;
	pid = fork();
	if (pid > 0) // 父进程
	{
		int i = 0;
		signal(10, sig_usr1); // 10号 SIGUSR1 信号
		signal(17, sig_chld); // 17号 SIGCHLD 信号
		while (1)
		{
			i++;
			printf("parent process i = %d\n", i);
			sleep(1);
		}
	}

	else if (pid == 0) // 子进程
	{
		sleep(3);
		kill(getppid(), 10);
		exit(0); // 相当于给父进程发送17号信号 kill(getppid, 17)
	}

	return 0;
}

1.5.2 程序运行结果

 fork 后父子进程各自运行,子进程等待三秒,父进程在 while(1) 打印信息。三秒后子进程给父进程发送 10 号与 17 号信号。父进程通过 signal 函数接收到子进程发过来的信号,去执行信号服务函数 sig_usr1 与 sig_chld 。

2. 信号灯

2.1 信号灯的概述

信号量集合(可以包含多个信号量)IPC 对象是一个信号的集合(多个信号量)

2.2 相关函数

函数原型:     int semget(key_t key, int nsems, int semflg);
//创建一个新的信号量或获取一个已经存在的信号量的键值。

所需头文件: 
              #include <sys/types.h>
              #include <sys/ipc.h>
              #include <sys/sem.h>
函数参数:
              key:和信号灯集关联的key值
              nsems:  信号灯集中包含的信号灯数目
              semflg:信号灯集的访问权限
函数返回值:
               成功:信号灯集ID
               出错:‐1
函数原型:int semctl ( int semid, int semnum,  int cmd,…union semun arg(不是地址));
//控制信号量,删除信号量或初始化信号量

所需头文件:
                 #include <sys/types.h>
                 #include <sys/ipc.h>
                 #include <sys/sem.h>
函数参数:
                 semid:信号灯集ID
                 semnum: 要修改的信号灯编号
                 cmd : 
                         GETVAL:获取信号灯的值
                         SETVAL:设置信号灯的值
                         IPC_RMID:从系统中删除信号灯集合
函数返回值:
                 成功:0
                 出错:‐1

2.3 信号灯 PV 操作

int semop(int semid ,struct sembuf *sops ,size_t nsops);
//用户改变信号量的值。也就是使用资源还是释放资源使用权

包含头文件:
            include<sys/sem.h>
参数:
       semid : 信号量的标识码。也就是semget()的返回值
       sops是一个指向结构体数组的指针。
           struct   sembuf{
                             unsigned short  sem_num;//信号灯编号;
                             short  sem_op;//对该信号量的操作。‐1 ,P操作,1 ,V操作
                             short sem_flg;0阻塞,1非阻塞
                          };
       nsops : 操作信号灯的个数

       //如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;如
果 nsops 的值为负数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于 nsops
的绝对值。通常用于获取资源的使用权;如果 nsops 的值为0,则操作将暂时阻塞,直到信号的值变为0。

2.4 代码示例

sem.c

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <unistd.h>

#define SEM_READ 0
#define SEM_WRITE 1

union semun
{
	int val;
};

void P_operation(int index, int semId)
{
	struct sembuf sop;
	sop.sem_num = index; //信号灯编号
	sop.sem_op = -1;	 // P 操作
	sop.sem_flg = 0;	 // 阻塞
	semop(semId, &sop, 1);
}

void V_operation(int index, int semId)
{
	struct sembuf sop;
	sop.sem_num = index; //信号灯编号
	sop.sem_op = 1;		 // V 操作
	sop.sem_flg = 0;	 // 阻塞
	semop(semId, &sop, 1);
}

int main()
{
	key_t key;
	key = ftok("b.c", 123); // 获取 key 值
	pid_t pid;
	int semId; // 信号灯 ID
	int shmId; // 共享内存 ID

	char *shamaddr;

	semId = semget(key, 2, IPC_CREAT | 0755); // 创建信号量
	if (semId < 0)
	{
		perror("semget error");
		return -1;
	}
	shmId = shmget(key, 128, IPC_CREAT | 0755); // 创建共享内存
	if (shmId < 0)
	{
		perror("shmget error");
		return -1;
	}

	// Init semaphore
	union semun myun;
	// Init semaphore read
	myun.val = 0;
	semctl(semId, SEM_READ, SETVAL, myun); // 对 SEM_READ 信号量设置初始值
	// Init semaphore write
	myun.val = 1;
	semctl(semId, SEM_WRITE, SETVAL, myun); // 对 SEM_WRITE 信号量设置初始值

	pid = fork();

	// child process
	if (pid == 0)
	{
		while (1)
		{
			shamaddr = (char *)shmat(shmId, NULL, 0);		// 共享内存映射
			P_operation(SEM_READ, semId);					// P 操作
			printf(" get share memory is: %s\n", shamaddr); // 操作对共享资源
			V_operation(SEM_WRITE, semId);					// V 操作
		}
	}

	// parent process
	else if (pid > 0)
	{
		while (1)
		{
			shamaddr = (char *)shmat(shmId, NULL, 0);
			P_operation(SEM_WRITE, semId);
			printf("please input to share memory\n");
			fgets(shamaddr, 32, stdin);
			V_operation(SEM_READ, semId);
		}
	}

	return 0;
}

程序运行结果:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值