Linux(信号,进程间通信)共享内存,信号量,消息队列

  1. 信号(signal)
    1.1 什么是信号?
    信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
    1.2 信号的来源
  1. 硬件
    [1] 用户在终端按下某些键时,终端驱动程序会发送信号给前台进程
    ctrl+c SIGINT 终止进程
    ctrl+z SIGTSTP 暂停进程
    ctrl+\ SIGQUIT 终止进程
    [2] 硬件异常产生信号,这些条件由硬件检测到并通知内核,然后内核向当前进程发送适当的信号。
    进程执行了除以0的指令 CPU的运算单元(ALU)会产生异常 SIGFPE信号发送给进程 终止进程
    进程访问了非法内存地址 MMU会产生异常
  2. 软件
    [1] 系统调用函数 kill(), raise()
    [2] shell命令 kill
    [3] 内核检测到某种软件条件发生时也可以通过信号通知进程
    子进程结束,内核给父进程发送SIGCHILD
    闹钟超时产生SIGALRM信号
    向读端已关闭的管道写数据时产生SIGPIPE信号。
    1.3 信号基本概念
    信号注册:内核更新目标进程的数据结构以表示一个新信号已被接受。
    信号的投递(Delivery):实际执行信号的处理动作称为信号投递。
    信号未决(Pending):信号从发生到投递之间的状态。
    信号部署(disposition):部署信号的处理策略(阻塞,忽略还是执行用户定义的处理函数)
    阻塞(Block)某个信号:被阻塞的信号产生后将保持在未决状态,直到进程解除对此信号的阻塞,才执行投递的动作。
    1.4 信号的生命周期
    信号产生(硬件或软件) -> 信号注册 -> 信号注销 -> 信号的投递(处理信号)
    信号投递的时机: 等到进程即将从内核态返回用户态的时候
    在其从内核空间返回到用户空间时会检测是否有信号等待处理。如果存在未决信号等待处理且该信号没有被进程阻塞,
    则投递该信号并执行相应的信号处理函数,然后进程会把信号在未决信号结构中注销(删除)掉。
    1.5 信号的处理流程
    系统调用或中断或异常 ->(用户态->内核态) 处理系统调用或中断或异常 -> 检测是否有信号等待处理 -> (内核态-> 用户态 )执行信号处理函数(信号投递) ->(用户态 -> 内核态 ) 系统调用或中断或异常 返回 -> (内核->用户态)从程序被打断的地方继续运行
    1.6 信号的响应方式
    1)忽略信号(ignore the signal):对信号不做任何处理
    2)捕捉信号(catch the signal with a signal handler):执行信号处理函数
    3)执行缺省操作(perform the default action):Linux对每种信号都规定了默认操作
    SIGSTOP、SIGTSTP 默认操作 暂停进程
    SIGKILL、SIGTERM、SIGINT、SIGQUIT 默认操作 终止进程
    man 7 signal 可以查询每个信号的默认动作
    注意: SIGKILL 和 SIGSTOP 不能被忽略,也不能被捕捉(它们向内核和超级用户提供一种使进程终止或停止的可靠方法。)
    SIGKILL 终止进程
    SIGSTOP 暂停进程

1.7 信号相关函数
【1】kill (重点掌握)
#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);
函数功能:
向指定的进程或进程组发送信号
函数参数:
[1] pid :用于指定PID和PGID
pid > 0 表示目标进程的进程号(PID)
pid = 0 表示将信号发送给同一进程组内的所有进程
pid = -1 表示将信号发送给所有进程(需要权限)
pid < -1 表示将信号发送给PGID=-pid进程组内的所有进程
[2] sig : 用于指定发送的信号类型
返回值:
成功 返回 0
失败 返回 -1

【2】raise
#include <signal.h>

int raise(int sig);
函数功能:
将信号发送给当前进程或者线程
函数参数:
[1] sig : 指定信号的类型
返回值:
成功 返回 0
失败 返回 !0
注意:
raise(sig) 等价于 kill(getpid(), sig)
或 pthread_kill(pthread_self(), sig);

【3】alarm
#include <unistd.h>

unsigned int alarm(unsigned int seconds);
函数功能:
alarm()也称为闹钟函数,它可以在进程中设置一个定时器。当定时器指定的时间到时,内核就向进程发送SIGALRM信号。
函数参数:
[1] seconds : 指定时间(秒)
返回值:
1) 设置过闹钟 返回 上一个闹钟的剩余时间
2) 没有设置过闹钟 返回 0

注意:
1)SIGALRM 默认动作是终止该进程
2)经过指定秒后,信号由内核产生,由于进程调度的延迟,进程得到控制能够处理该信号还需一段时间,所以该设置的时间不会非常准确。
3)每个进程有且只能有一个闹钟,新闹钟会替代旧闹钟。
4)如果参数seconds为0,则之前设置的闹钟会被取消

【4】signal 信号部署函数
#include <signal.h>

void (*signal(int sig, void (*func)(int)))(int);

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
函数功能 :
部署信号的响应方式
函数参数 :
[1] signum : 用于指定信号的类型
[2] handler : 用于指定响应方式
SIG_IGN ---- 忽略信号
SIG_DFL ---- 执行缺省操作
函数地址 ---- 捕捉信号
函数返回值 :
失败 返回 SIG_ERR
成功 返回 信号原先的处理方式

注意:
信号处理函数中的形参,用于指定信号的类型

1.8 信号机制实验
【实验目的】
1.了解什么是信号。
2.熟悉LINUX系统中进程之间软中断通信的基本原理。
【实验原理】
利用signal来实现发送信号和接受信号的原理
【实验内容】
创建子进程代表售票员,父进程代表司机 ,同步过程如下:
售票员捕捉SIGINT(代表开车),发SIGUSR1给司机,司机打印(“let’s gogogo”)
售票员捕捉SIGQUIT(代表停车),发SIGUSR2给司机,司机打印(“stop the bus”)
司机捕捉SIGTSTP(代表车到总站),发SIGUSR1给售票员,售票员打印(“please get off the bus”)

ctrl+c SIGINT 终止进程
ctrl+z SIGTSTP 暂停进程
ctrl+\ SIGQUIT 终止进程

先分析父进程和子进程可能收到的信号以及处理策略
父进程(司机 dirver) 子进程(售票员 saler)
SIGINT 忽略 SIGINT 捕捉
SIGUSR1 捕捉
SIGQUIT 忽略 SIGQUIT 捕捉
SIGUSR2 捕捉
SIGTSTP 捕捉 SIGTSTP 忽略
SIGUSR1 捕捉

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

pid_t pid;

void driver_handler(int signo);
void saler_handler(int signo);

int main(int argc, char *argv[])
{

   if ((pid = fork()) == -1)
	   {
		 perror("fork");
		 exit(-1);
	   } 
   if (pid > 0)	/* parent driver */
   {
		 signal(SIGTSTP, driver_handler);
		 signal(SIGINT, SIG_IGN);
		 signal(SIGQUIT, SIG_IGN);
		 signal(SIGUSR1, driver_handler);
		 signal(SIGUSR2, driver_handler);
		 while (1) pause();
	   }
	   else	/* child saler */
	   {
		 signal(SIGINT, saler_handler);
		 signal(SIGQUIT, saler_handler);
		 signal(SIGTSTP, SIG_IGN);
		 signal(SIGUSR1, saler_handler);
		 signal(SIGUSR2, SIG_IGN);
		 while (1) pause();
	   }

	   return 0;
}

void driver_handler(int signo)
{
	  if (signo == SIGUSR1)
		printf("Let's gogogo!\n");
	  if (signo == SIGUSR2)
		printf("Stop the bus!\n");
	  if (signo == SIGTSTP)
		kill(pid, SIGUSR1);
}

void saler_handler(int signo)
{
  pid_t ppid = getppid();
	  if (signo == SIGINT)
		kill(ppid, SIGUSR1);
	  if (signo == SIGQUIT)
		kill(ppid, SIGUSR2);
	  if (signo == SIGUSR1)
	  {
		printf("please get off the bus\n");
		kill(ppid, SIGKILL);
		exit(0);
	  }
}
  1. System V 进程间通信

共享内存、信号量、消息队列
2.1 System V IPC对象
当使用 System V 这三种通信方式 共享内存、信号量、消息队列。内核会自动创建三种数据结构,来描述我们使用的
这三种通信方式。IPC对象存在于内核中,作为桥梁供多进程操作进行数据通讯。

需要注意的是IPC对象是系统范围内起作用的,因此如果创建后进程不删除它们就退出则会遗留在系统里
此系统也提供了命令来维护:
[1]查看IPC对象信息
命令:ipcs [-aqms]
参数说明:
1)-a:查看全部IPC对象信息。(all)
2)-q:查看消息队列信息。(queue)
3)-m:查看共享内存信息。(memory)
4)-s:查看信号量信息。(semaphore)

[2]创建IPC对象
命令:ipcmk [-M size] [-S nsems] [-Q]
参数说明:
1)-M:创建共享内存, size指创建共享内存段的大小。
2)-S:创建信号量,nsemsz指信号量的个数。
3)-Q:创建消息队列。

[3]删除IPC对象
命令1:ipcrm -[qms] ID
命令2:ipcrm -[QMS] key
参数说明:
1)-q或-Q:删除消息队列信息。
2)-m或-M:删除共享内存信息。
3)-s或-S:删除信号量信息。
注意事项:如果指定了qms,则用IPC对象的标识符(ID)作为输入;如果指定了QMS,则用IPC对象的键值(key)作为输入。
2.2 IPC标识符
非负的整数
每个IPC对象的唯一标识符
shmid、semid、msqid
2.3 IPC键
IPC键就是IPC对象的外部名,通过"键"的使用也使得一个IPC对象能为多个进程所共用。(类比文件IO的pathname)
数据类型 :key_t, 通常在头文件<sys/types.h>被定义为是一个非负长整数。

注意:
相同类型的IPC对象,IPC key 与 IPC 标识符 唯一对应
不同类型的IPC对象,IPC key 与 IPC标识符 不是唯一对应的
2.4 获取IPC键
方法一:
#include <sys/types.h>
#include <sys/ipc.h>

key_t ftok(const char *pathname, int proj_id);//推荐
函数功能:
获取IPC键
函数参数:
[1] pathname : 文件的路径(已存在的、可以访问的文件的路径)
[2] proj_id : !0整数(低8bit是有效的)
返回值:
成功 返回 IPC键
失败 返回 -1

方法二:
key = IPC_PRIVATE (0x00000000)
此时 key 与 ID 没有对应关系,导致没有亲缘关系的进程无法通过 key 获取 ID,
因此 key = IPC_PRIVATE 创建的IPC对象只能用于有亲缘关系的进程通信

  1. 共享内存
    3.1 什么是共享内存
    共享内存是一种最为高效的进程间通信方式
    4G进程空间中专门开辟了一块地址范围用于映射共享内存。
    实际上就是将进程的虚拟空间的某些页映射到同一个物理页,这样就实现了共享内存。
    3.2 特点
    1)进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。
    2)由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等 。
    3.3 共享内存的操作流程
    1)创建/打开共享内存 shmget
    2)映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问 shmat
    3)撤销共享内存映射 shmdt
    4)删除共享内存对象 shmctl
    3.4 共享内存 API
    【1】shmget
    #include <sys/ipc.h>
    #include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);
函数功能 :
申请共享内存
函数参数 :
[1] key : IPC 键(ftok 或 IPC_PRIVATE)
[2] size : 共享内存的大小(建议设置为 页长4096的整数倍) 只获取共享内存时可以指定为0.
[3] shmflg : 用于设置标志
IPC_CREAT 创建共享内存  只获取共享内存时指定为0s
IPC_EXCL 会和IPC_CREAT一起使用,表示如果IPC对象已存在则报错
mode IPC对象的访问权限,同文件权限(执行权限可以忽略)
返回值:
成功 返回 共享内存的IPC 标识符 shmid
失败 返回 -1
【2】shmat (attaches)
#include <sys/types.h>
#include <sys/shm.h>

void *shmat(int shmid, const void *shmaddr, int shmflg);
函数功能:
将共享内存和进程的虚拟空间做映射
函数参数:
[1] shmid : 共享内存的 IPC标识符
[2] shmaddr : 指定共享内存映射到的虚拟地址 (NULL 表示操作系统选择合适的空间映射) //推荐NULL
[3] shmflg :
SHM_RDONLY 共享内存只读
0 读写
返回值:
成功 返回 映射后的共享内存虚拟地址
失败 返回 (void *) -1

【3】shmdt (detach)
int shmdt(const void *shmaddr);
函数功能:
解除共享内存映射
函数参数:
[1] shmaddr : 共享内存的地址
返回值
成功 返回 0
失败 返回 -1

【4】shmctl
#include <sys/ipc.h>
#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
函数功能: 控制共享内存(删除共享内存)
函数参数:
[1] shmid : 共享内存的IPC 标识符
[2] cmd : 指定功能
IPC_RMID 销毁共享内存
[3] buf : IPC_STAT的时候,取得的状态放在这个结构体中。如果要改变共享内存的状态,用这个结构体指定;
IPC_RMID NULL
返回值:
成功 返回 0
失败 返回 -1
注意:
1)cmd为IPC_RMID 命令实际上不从内核删除一个段,而是仅仅把这个段标记为删除,实际删除过程是发生在最后一个使用该共享内存的进程退出或者是解除映射之后。

【5】system
#include <stdlib.h>

int system(const char *command);
函数功能:
执行shell命令
函数参数:
[1] command : 需要执行的shell命令
返回值:
出错 返回 -1
成功 返回 0

4.System V 信号灯(信号量)
信号灯(semaphore),也叫信号量。是一种进程间通信的方式,只不过它和管道、FIFO或者共享内存不一样,它是不同进程间或一个给定进程内部不同线程间同步的机制。
它的发明来源于火车运行系统中的"信号灯",利用信号灯可以实现"PV"操作。
信号量的三种操作:
回忆线程时我们说过,信号量是一类受保护的变量,只能通过一下三种方式访问
1)信号量初始化
初始化就是指定某类共享资源初始可以用的值。
2)p操作(申请资源)
if (信号量的值 > 0) {
申请资源的任务继续;信号量值–;
} else {
申请资源的任务阻塞
}
3)V操作(释放资源)
if(没有任务在等待该资源){
信号量的值加一;
}else{
唤醒第一个等待的任务,让其继续运行;
}

4.1 System V 信号量(信号量数组)的操作函数
【1】semget 申请/创建 System V 信号量
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget(key_t key, int nsems, int semflg);
函数参数:
[1] key : IPC键
[2] nsems : 指定信号数组的元素个数
[3] semflg : 用法同共享内存
IPC_CREAT 信号量不存在则创建
IPC_EXCL 和IPC_CREAT一起使用,已存在则报错
mode 访问权限
返回值:
成功 返回 信号量的标识符(semid)
失败 返回 -1

【2】semctl 控制信号量(初始化信号量、销毁信号量、获取信号量的值)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, …);
函数参数:
[1] semid : 信号量的标识符
[2] semnum : 要操作的信号灯编号(创建信号灯集时,信号灯的编号从0开始)
[3] cmd : 选择功能
SETVAL 设置信号量的值(对信号量初始化)
GETVAL 获取信号量的值
IPC_RMID 销毁信号量 (此时semnum无效 )
[4] union semun arg : (可变参数)是否使用取决于选择的功能,注意union semun 共用体使用者必须自己定义
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) */
};
返回值 :
失败 返回 -1
成功 根据选择的功能 来决定
GETVAL 返回 当前信号量的值
其他 返回 0
注意:
意:IPC_RMID从系统中删除该信号灯集合。这种删除是立即发生的。删除时任使用此信号量集合的其他进程,
在它们下次试图对此信号量结合操作是将出错返回EIDRM。

1)封装初始化功能:

/* 函数参数:
semid 信号量的标识符
semnum 信号量的编号
semval 信号量的值

返回值:
成功 返回 0
失败 返回 -1
int mysem_init(int semid, int semnum, int semvl); */

2)销毁信号量
/* 函数参数:
semid 信号量的标识符

返回值:
成功 返回 0
失败 返回 -1

int mysem_destroy(int semid); */

3)获取信号量的值

函数参数:
semid 信号量的标识符
semum 信号量的编号

返回值:
成功 返回 信号量的值
失败 返回 -1

int mysem_getvalue(int semid, int semnum);

【3】sem_op 操作信号量(p操作、v操作)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

int semop(int semid, struct sembuf *sops, unsigned nsops);
函数参数:
[1] semid : 信号量的IPC标识符
[2] sops : 用于指定对信号量的操纵
struct sembuf {
short sem_num; // 要操作的信号灯的编号
short sem_op; // 0 : 调用者阻塞等待,直到信号灯的值等于0时返回。可以用来测试共享资源是否已用完。
// 1 : 释放资源,V操作
// -1 : 申请资源,P操作
short sem_flg; //
a. 0 代表阻塞调用(资源不满足阻塞)
b. IPC_NOWAIT 代表非阻塞调用(资源不满足通常返回出错)
c. 如果设置了SEM_UNDO标志,那么在进程结束时,系统会自动释放该进程中未释放的信号量(阻塞)
};
[3] nsops : 操作的信号量个数(1)
返回值:
成功 返回 0
失败 返回 -1

5.消息队列
5.1 基本概念
[1] 什么是消息队列? 4
消息队列是由存放在内核中由消息组成的链表
[2] 消息队列的特点
用户可以在消息队列中添加消息(添加到消息队列的末尾)和读取消息等。(这点看有一定FIFO的特性)
消息队列可以按照类型来接收发送数据。(比FIFO具有更大的优势)
[3] 消息队列的操作
由msgget创建新队列或打开已经存在的队列
由msgsnd将消息添加到消息队列尾,每个消息包括正整数标识的类型,非负的长度,及数据。
由msgrcv从消息队列中取消息,不必按FIFO取消息,可以通过类型字段取相应的消息。
由msgctl删除消息队列
[4] 操作函数

1】msgget
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
nt msgget(key_t key, int msgflg);
函数参数 :
[1] IPC 键
[2] msgflg 标志 (用法同 共享和信号量)
IPC_CREAT 创建消息队列
IPC_EXCL 和IPC_CREAT一起使用,已存在则报错
mode 访问权限
返回值:
成功 返回 ID
失败 返回 -1
【2】msgsnd
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
函数参数:
[1]msqid : 消息队列的IPC标识符
[2]msgp : 指定向消息队列中添加的消息
[3]msgsz : 消息的正文长度(sizeof(struct msgbuf)-sizeof(long))
[4]msgflg: 标志
IPC_NOWAIT 非阻塞操作,消息队列中没有可用空间时会立刻返回出错
0 阻塞操作,消息队列中没有可用空间时将会阻塞
返回值:
出错 返回 -1
成功 返回 0

注意: 消息结构需要用户自行定义
参考模型
struct msgbuf {
long mtype; /* message type, must be > 0 / 消息类型 (> 0 的整数)
char mtext[1]; /
message data */ 消息的正文(消息体) 由用户自行定义
};
【3】msgrcv
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
函数参数:
[1]msqid : 消息队列的IPC标识符
[2]msgp : 用于保存从消息队列中获取的消息
[3]msgsz : 消息的正文长度(sizeof(struct msgbuf)-sizeof(long))
[4]msgtyp : 用于指定消息的类型
msgtyp = 0,获取消息队列的第一个消息
msgtyp > 0, 获取消息类型=msgtyp的第一个消息
msgtyp < 0, 获取消息类型<=|msgtyp|中最小的一个消息
[5]msgflg : 标志
IPC_NOWAIT 非阻塞操作,消息队列中没有指定的消息时,立刻返回出错
0 阻塞操作,消息队列中没有指定的消息时,一直阻塞直到有消息为止
返回值:
成功 返回 消息正文实际长度
失败 返回 -1
【4】msgctl
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
函数功能:
控制消息队列(删除消息队列)
函数参数:
[1] msgqid : 消息队列的标识符
[2] cmd : 选择功能
IPC_RMID 删除消息队列 删除一个消息队列。执行该命令系统会立刻把该消息队列从内核中删除,该消息队列中的所有消息将会被丢弃。
[3] buf :
IPC_RMID 可以忽略该参数(NULL)
返回值:
成功 返回 0
失败 返回 -1

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值