- 进程间通信
- 1.进程间通信(IPC)方式
- 2.无名管道(PIPE)和有名管道(FIFO)
- ①无名管道(PIPE)
- ②有名管道 FIFO
- 3.信号
- ①信号概念
- ②linux常规信号 kill -l
- ③信号分类
- ④相关函数
- ⑤信号的内核数据模型
- 4.system V-IPC 共享内存、消息队列、信号量
- ①IPC对象
①无名管道(PIPE)和有名管道(FIFO)
②信号(signal)
③system V-IPC 之共享内存
④system V-IPC 之消息队列
⑤system V-IPC 之信号量
⑥套接字socket
特点:
无名管道是最简单的常用于一对一的亲缘进程间通信的方式
有名管道存在于文件系统之中,提供写入原子性特征,信号是唯一一种异步通信方式
共享内存的效率最高,但是要结合信号量等同步互斥机制一起使用
消息队列提供一种带简单消息标识的通信方
套接字是一种更为宽泛意义上的进程间通信方式——它允许进程间跨网络
2.无名管道(PIPE)和有名管道(FIFO) ①无名管道(PIPE)特点:
1,没有名字,因此无法使用 open( )
2,只能用于亲缘进程间(比如父子进程、兄弟进程、祖孙进程……)通信
3,半双工工作方式:读写端分开
4,写入操作不具有原子性,因此只能用于一对一的简单通信情形
5,不能使用 lseek( )来定位
创建无名管道pipe函数
功能 创建无名管道:PIPE 头文件 #include原型 int pipe(int pipefd[2]); 参数 pipefd:一个至少具有2个int型数据的数组,用来存放PIPE的读写端描述符 返回值:成功 0 失败 -1
需要注意的是:无名管道的读写端是固定的
fd[0]:读端,只能进行读操作
fd[1]:写端,只能进行写操作
数据读写的时候
(1)写数据进程已经死了,管道中依然会存放写入的数据
(2)读端读数据的时候,发现管道中没有数据,会产生阻塞,一直等待管道中有数据
(3)管道中的数据一旦读完,管道就空了,不能在没有第二次写入数据之前连续多次读取,如果管道中的数据,没有读取完,是可以直接进行第二次读取的
②有名管道 FIFO特点:
1,有名字,存储于普通文件系统之中
2,任何具有相应权限的进程都可以使用 open( )来获取 FIFO 的文件描述符
3,跟普通文件一样:使用统一的 read( )/write( )来读写
4,跟普通文件不同:不能使用 lseek( )来定位,原因同 PIPE
5,具有写入原子性,支持多写者同时进行写操作而数据不会互相践踏
6,First In First Out,最先被写入 FIFO 的数据,最先被读出来
创建有名管道函数
功能 创建有名管道:FIFO 头文件 #include#include 原型 int mkfifo(const char *pathname, mode_t mode); 参数 pathname:FIFO的文件名 mode:文件权限 返回值:成功 0 失败 -1
access函数
#includeint access(const char *pathname, int mode); //检查文件是否具有可读可写可执行,是否存在
读写要阻塞的情况
需要注意的是:
管道文件(pipe,FIFO,socket)不可以在只有读端或者只有写端的情况下被打开
有名管道应用:
3.信号 ①信号概念 信号是系统软件层次上对中断机制的一种模拟(软中断方式),是一种异步通信机制。
- (1)信号的生命周期
进程信号产生、信号注册、信号响应和处理,信号注销
- (2)信号响应方式
①忽略信号(信号SIGKILL和SIGSTOP是两个特殊的信号,他们不能被忽略、阻塞或捕捉)
②捕捉信号响应函数(自定义信号处理函数,当收到信号的时候,响应自定义事件)
③执行缺省动作(Linux系统下每一种信号都有规定的默认操作)
信号 SIGKILL 和 SIGSTOP 是两个特殊的信号,他们不能被忽略、阻塞或捕捉,只能按缺省动作来响应。
除了这两个信号之外的其他信号,接收信号的目标进程按照如下顺序来做出反应:
-
A) 如果该信号被阻塞,那么将该信号挂起,不对其做任何处理,等到解除对其阻塞为止。否则进入 B。
-
B) 如果该信号被捕捉,那么进一步判断捕捉的类型:
- B1) 如果设置了响应函数,那么执行该响应函数。
- B2) 如果设置为忽略,那么直接丢弃该信号。否则进入 C。
-
C) 执行该信号的缺省动作。
SIGHUP: 当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程 SIGINT:当用户按下了③信号分类组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动 作为终止进程。 SIGQUIT:当用户按下 组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信 号。默认动作为终止进程。 SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件 SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。 SIGABRT: 调用abort函数时产生该信号。默认动作为终止进程并产生core文件。 SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。 SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。 SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。 SIGUSE1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。 SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。 SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。 SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。 SIGALRM: 定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程。 SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。 SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。 SIGCHLD:子进程结束时,父进程会收到这个信号。默认动作为忽略这个信号。 SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。 SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。 SIGTSTP:停止终端交互进程的运行。按下 组合键时发出这个信号。默认动作为暂停进程。 SIGTTIN:后台进程读终端控制台。默认动作为暂停进程。 SIGTTOU: 该信号类似于SIGTTIN,在后台进程要向终端输出数据时发生。默认动作为暂停进程。 SIGURG:套接字上有紧急数据时,向当前正在运行的进程发出些信号,报告有紧急数据到达。如网络带外数据到达,默认动作为忽略该信号。 SIGXCPU:进程执行时间超过了分配给该进程的CPU时间 ,系统产生该信号并发送给该进程。默认动作为终止进程。 SIGXFSZ:超过文件的最大长度设置。默认动作为终止进程。 SIGVTALRM:虚拟时钟超时时产生该信号。类似于SIGALRM,但是该信号只计算该进程占用CPU的使用时间。默认动作为终止进程。 SGIPROF:类似于SIGVTALRM,它不公包括该进程占用CPU时间还包括执行系统调用时间。默认动作为终止进程。 SIGWINCH:窗口变化大小时发出。默认动作为忽略该信号。 SIGIO:此信号向进程指示发出了一个异步IO事件。默认动作为忽略。 SIGPWR:关机。默认动作为终止进程。 SIGSYS:无效的系统调用。默认动作为终止进程并产生core文件。 SIGRTMIN ~ (64) SIGRTMAX:LINUX的实时信号,它们没有固定的含义(可以由用户自定义)。所有的实时信号的默认动作都为终止进程。
1、非实时信号(不可靠信号)
前31个,特点
1,非实时信号不排队,信号的响应会相互嵌套。
2,如果目标进程没有及时响应非实时信号,那么随后到达的该信号将会被丢弃。
3,每一个非实时信号都对应一个系统事件,当这个事件发生时,将产生这个信号。
4,如果进程的挂起信号中含有实时和非实时信号,那么进程优先响应实时信号并且会
从大到小依此响应,而非实时信号没有固定的次序。
2、实时信号(可靠信号)
后31个,特点
1,实时信号的响应次序按接收顺序排队,不嵌套。
2,即使相同的实时信号被同时发送多次,也不会被丢弃,而会依次挨个响应。
3,实时信号没有特殊的系统事件与之对应。
④相关函数1、kill发送信号
功能 向指定进程或者进程组,发送一个指定的信号 头文件 #include#include 原型 int kill(pid_t pid, int sig); 参数 pid 小于-1: 信号将被发送给组ID等于-pid 的进程组里面的所有进程 -1: 信号将被发送给所有进程(如果当前进程对其有权限) 0: 信号将被发送给与当前进程同一个进程组内的所有进程 大于0: 信号将被发送给PID等于pid的指定进程 sig 要发送的信号 返回值 成功0 失败-1
2、signal响应信号注册
功能 捕捉一个指定的信号,即预先为某信号的到来做好准备 头文件 #include原型 void (*signal(int sig, void (*func)(int)))(int); 参数 sig 要捕捉的信号 func SIG_IGN 捕捉动作为:忽略 SIG_DFL 捕捉动作为:执行该信号的缺省动作 void (*p)(int) 捕捉动作为:执行由p指向的信号响应函数 返回值 成功 最近一次调用该函数时第二个参数的值 失败 SIG_ERR
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
这个signal函数需要的参数是一个int型变量,和一个函数指针,返回值也是一个函数指针
3、raise给自己发送信号
功能 自己给自己发送一个指定的信号 头文件 #include原型 int raise(int sig); 参数 sig要唤醒(发送)的信号 返回值 成功 0 失败 非0
4、pause等待信号
功能 将本进程挂起,直到收到一个信号 头文件 #include原型 int pause(void); 参数 无 返回值 收到非致命信号或者已经被捕捉的信号 -1 收到致命信号导致进程异常退出 不返回
5、信号集和阻塞sigemptyset、sigfillset、sigaddset、sigdelset、sigismember
功能 信号集操作函数簇: 1,sigemptyset(): 将信号集清空 2,sigfillset(): 将所有信号添加到信号集中 3,sigaddset( ): 将指定的一个信号添加到信号集中 4,sigdelset(): 将指定的一个信号从信号集中剔除 5,sigismember( ): 判断一个指定的信号是否被信号集包含 头文件 #include原型 int sigemptyset(sigset_t *set);int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signum);int sigdelset(sigset_t *set, int signum); int sigismember(const sigset_t *set, int signum); 参数 set 信号集 signum 要添加,或者剔除,或者判断的信号 返回值 成功 sigismember()返回1,其余函数返回0 失败 sigismember()返回0,其余函数返回-1
6、sigprocmask设置阻塞或解除阻塞
功能 阻塞或者解除阻塞一个或者多个信号 头文件 #include原型 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); 参数 how SIG_BLOCK 在原有阻塞的信号基础上,再添加set中的信号 sIG_SETMASK 将原有阻塞的信号,替换为set中的信号 SIG_UNBLOCK 在原有阻塞的信号基础上,解除set中的信号 set 信号集 oldset 原有的信号集 返回值 成功 0 失败 -1
7、sigqueue发生信号数据带数据
功能 给某进程发送一个指定的信号,同时携带一些数据 头文件 #include原型 int sigqueue(pid_t pid, int sig, const union sigval value); 参数 pid 目标进程PID sig 要发送的信号 value 携带的额外数据 返回值 成功 0 失败 -1 备注 目标进程能获取由sigqueue()发送过去的额外数据 value的前提是:必须设置sA_SIGINFO标识。
union sigval
{
int sival_int;
void * sival_prt;
};
unio sigva data;
data.sival_int = 100;
发送信号:kill(进程号, 信号);
发送信号带数据:sigqueue(进程号,信号,data);
8、sigaction捕捉信号,获取数据
功能 捕捉一个指定的信号,且可以通过扩展响应函数来获取信号携带的额外数据 头文件 #include原型 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 参数 signum 要捕捉的信号 act sa_handler 标准信号响应函数指针 sa_sigaction 扩展信号响应函数指针 sa_mask 临时信号阻塞掩码 sa_flags: SA_NOCLDSTOP 子进程暂停时不提醒 SA_NOCLDWAIT 使子进程死亡时跳过僵尸态 SA_NODEFER 不屏蔽来自本信号响应函数内部的信号 SA_ONSTACK 信号响应函数在替补栈中分配内存 SA_RESETHAND 响应函数执行一遍之后重置该信号响应策略 SA_RESTART 自动重启被该信号中断的某些系统调用 SA_SIGINFO 使用扩展信号响应函数而不是标准响应函数 sa_restorer 废弃的接口 oldact 原有的信号处理参数 返回值 成功 0 失败 -1
struct sigaction
{
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
};
1,标准信号响应函数指针sa_handler和扩展信号响应函数指针sa_sigaction所指向的函数接口是不同的,sa_sigaction指向的函数接口要复杂得多,事实上如果选择扩展接口的话,信号的接收进程不仅可以接收到 int型的信号,还会接收到一个siginfo_t型的结构体指针,还有一个void型的指针。
2,临时信号掩码sa_mask的设置使用信号集操作函数来操作,被设置在该掩码中的信号,在进程响应本信号期间被临时阻塞。
3,对sa_flags中的几个选项再做几点说明:
A) SA_NOCLDSTOP和SA_NOCLDWAIT只在对信号SIGCHLD设置响应函数时有效。
B)缺省情况下,当进程正在执行信号X的响应函数且未完,又接收到除信号X之外的其他信号时会嵌套响应,而如果收到的是信号X本身则不会嵌套。而设置了SA_NODEFER选项之后,则收到信号×本身也会嵌套响应了。
C)选项SA_ONSTACK涉及关于所谓“替补栈”的概念,一个进程的栈空间是非常有限的(2MB -8MB左右),当栈发生溢出的时候,操作系统将会触发一个SIGSEGV信号,如果捕捉这个信号,那么响应函数显然不能再在标准栈中分配了,此时只能通过替补栈来执行响应函数。
使用替补栈的顺序是:
(一) 在堆中申请一块内存,作为替补栈空间。
(二) 使用sigaltstack( )通知内核以上替补栈的位置和大小。
(三) 当使用sigaction( )为某信号设置响应函数时,将SA_ONSTACK位或到sa_flags 中,使得该响应函数在替补栈中运行。
4,一些系统调用函数在执行的时候可能会阻塞,在其阻塞期间如果接收到某个被捕捉的信号,那么缺省情况,在执行完信号响应函数之后他们会出错返回,且错误码会被设置为EINTR。这样的函数包括:
文件操作相关:
read()、readv()、write()、writev()
ioctl()注:当操作慢速设备(可能会引发阻塞且时间不确定的设备)上时。
打开文件相关:
open()注:比如按O_RDONLY或者O_WRONLY打开一个有名管道FIFO时。
进程等待相关:
wait()、wait3()、wait4()、waitid()、waitpid()
套接字相关:
accept()、connect()、recv()、recvfrom()、recvmsg()、send()、sendto()、sendmsg()
记录锁相关:
flock()
fcntl()注:当使用F_SETLKW时。消息队列相关:
mq_receive()
mq_timedreceive()、mq_send()、mq_timedsend()
同步互斥相关:
sem_wait()、sem_timedwait()
futex()注:当使用FUTEX_WAIT时
当我们在调用以上这些函数的时候要注意,由于他们在缺省情况下会被信号响应函数所中断,而这种中断并非是一个真正的错误(Real-Error),因此一般都需要重新启动这些函数。
重启这些函数的方法有两种:
第一,在 sigaction中的sa_flags 中设置SA_RESTART,让其自动重启。"
第二,判断这些函数的返回值和错误码,如果确实被信号所中断,那么手工重启。还有一些函数跟上面的函数恰好相反,他们不管有没有设置SA_RESTART,在他们运行的过程当中只要遇到信号捕捉,就一律出错返回并设置errno为EINTR,他们是:
套接字相关:(注:被设置了SO_RCVTIMEO或者SO_SNDTIMEO的情况下)
setsockopt()、accept()、recv()、recvfrom()、recvmsg()、connect()、send()、sendto()、sendmsg()
信号等待相关:
pause()、sigsuspend()、sigtimedwait()、sigwaitinfo()
文件描述符多路复用相关;
epoll_wait()
epool_pwait()、poll()
ppol()、select()、pselect()
system-V的IPC接口相关:
msgrcv()
msgsnd()
semop()、semtimedop()
睡眠相关:
clock_nanosleep()、nanosleep()、usleep()
5,如果需要使用扩展信号响应函数,则 sa_flags必须设置SA_SIGINFO,此时,结构体act中的成员sa_sigaction将会替代sa_handler(事实上他们是联合体里面的两个成员,是非此即彼的关系),扩展的响应函数接口如下:
void(*sa_sigaction)(int,siginfo_t *, void *);该函数的参数列表详情:
第一个参数:int型,就是触发该函数的信号。
第二个参数: siginfo_t型指针,指向如下结构体:
siginfo_t {
/ si_signo、si_errno 和 si_code 对所有信号都有效
int si_signo;
int si_errno;
int si_code;
int si_trapno;
pid_t si_pid;
uid_t si_uid;
int si_status;
clock_t si_utime;
clock_t si_stime;
union sigval si_value;
int si_int;
void *si_ptr;
int si_overrun;
int si_timerid;
void *si_addr;
long si_band;
int si_fd;
short si_addr_lsb;
void *si_lower;
void *si_upper;
int si_pkey;
void *si_call_addr;
int si_syscall;
unsigned int si_arch;
}
注意以上成员除了si_signo、si_errno. si_code之外的其他成员,是以部分联合体的形式传递的,并不全都有效。常用到的几个是:
A)发送者进程使用kill( )/sigqueue( )发送信号时si_pid和 si_uid将会被填充为其PID及其实际用户ID,另外如果使用的是sigqueue( )发送信号,那么si_int 和 si_ptr为其发送的额外数据。
B)当触发该响应函数的信号是SIGCHLD时,si_pid、si_uid、si_status、si_utime和si_stime会被填充。si_pid是子进程的PID,si_uid是子进程的实际用户ID,si_status是子进程的退出值或者导致子进程退出的信号值, si_utime和 si_stime分别包含子进程的用户空间执行时间以及系统空间执行时间。
C)当触发该响应函数的信号是SIGILL、SIGFPE、SIGSEGV、SIGBUS或者SIGTRAP时,si_addr将会被填充为发生该信号瞬间的内存地址,在某些平台中还会将si_trapno填充为触发该响应函数的信号值。
D)当触发该响应函数的信号是SIGPOLL和SIGIO时,si_band是一个位掩码,其被填充的内容等同于poll()中的revent,si_fd则会被填充为触发I/O事件的文件描述符。
E) si_code存放了触发该信号响应函数的信号来因,比如他可以是以下值:
SI_USER 由用户调用kilK()或raise( )触发 SI_KERNEL 由内核触发 SI_QUEUE 由用户调用sigqueue( )触发 SI_TIMER 由定时器触发 CLD_EXITED 由子进程正常退出触发,只对SIGCHLD有效 CLD_KILLED 由子进程被杀死触发,只对SIGCHLD有效 CLD_DUMPED 由子进程异常退出触发,只对SIGCHLD有效 POLL_IN 由输入数据有效触发,只对SIGPOLL有效 POLL_OUT 由输出缓冲区有效触发,只对SIGPOLL有效 POLL_MSG 由输入信息有效触发,只对SIGPOLL有效 POLL_ERR 由I/O错误触发,只对SIGPOLL有效
第三个参数:一个void型指针,该指针指向一个上下文环境,一般很少使用。
#ifndef __MYHEAD_H_ #define __MYHEAD_H_ #include#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define FIFODIR "/tmp/myfifo" #endif
#include "myhead.h"
void fsigaction(int sig,siginfo_t *siginfo,void *p)
{
printf("sig = %dn",sig);
if(siginfo->si_code == SI_QUEUE) //由sigqueue触发
{
printf("接收到的数据 siginfo->si_int = %dn",siginfo->si_int);
}
}
void fstand(int sig)
{
printf("sig = %dn",sig);
}
int main(int argc, char const *argv[])
{
pid_t _pid;
_pid = fork();
if(_pid < 0)
{
perror("fork()");
exit(-1);
}
if(_pid > 0) //father
{
sleep(1);
printf("父进程开始运行n");
// printf("_pid %dn",_pid);
union sigval data;
data.sival_int = 100;
sigqueue(_pid,2,data);
printf("发生信号带数据成功!n");
sleep(3);
wait(NULL);
printf("父进程退出n");
}
if(_pid == 0) //son
{
// printf("son pid %dn",getpid());
printf("----子进程开始运行n");
struct sigaction act;
memset(&act,0,sizeof(act));
act.sa_sigaction = fsigaction; //扩展信号响应函数
act.sa_flags |= SA_SIGINFO; //设置响应方式为扩展响应函数
// act.sa_handler = fstand;
sigaction(2,&act,NULL);
pause(); //等待信号
printf("----子进程退出n");
}
if(_pid == -1)
{
perror("fork()");
exit(0);
}
return 0;
}
sigaction()即可以使用扩展响应信号函数,也可以使用标准信号响应函数
signal跟kill配对使用,sigqueue跟sigaction配对使用
⑤信号的内核数据模型1,每一个线程都使用一个PCB(即 task_struct)来表示,因此pending(不是指针)就是一个线程单独私有的,当我们使用pthread_kill()给一个指定的线程发送某信号时,这些信号将会被存储在这个链队列中。
2,signal是一个指向线程共享的信号挂起队列相关结构体的指针,实际上,一个线程组(即一个进程)中的所有线程中的signal指针都指向同一个结构体,当我们使用诸如kill()来给一个进程发送某信号的时候,这些信号将会被存储在 shared_pending这个线程共享的链队列中。
如果一个进程中有超过1条线程,那么这些共享的挂起信号将会被随机的某条线程响应,为了能确保让一个指定的线程响应来自进程之外的、发送给整个进程的某信号,一般的做法如下:
除了指定要响应某信号的线程外,其他线程对这些信号设置阻塞。即使用sigprocmask()或者pthread_sigmask( )将这些需要阻塞的信号添加到信号阻塞掩码blocked当中。
3,sighand也是一个指针,因此也是进程中的所有线程共享的,他指向跟信号响应函数相关的数据结构,结构体struct sighand_struct中的数组 action有64个元素,一一对应Linux系统支持的64个信号(其中0号信号是测试用的,32号和33号信号保留),每一个元素是一个sigaction结构体,其成员就是标准C库函数sigaction()中的第二个参数的成员,可见,该函数相当于是一个应用层给内核设置信号响应策略的窗口。
进程间的通信,管道和信号,很多通信要求无法满足,就有IPC对象
IPC对象:消息队列,共享内存,信号量
IPC对象使用之前需要相同的键值
获取键值key,ftok()
功能
获取一个当前未用的IPC的 key
头文件
#include
#include
原型
key_t ftok(const char *pathname, int proj_id);
参数
pathname 个合法的路径
proj_id 一个整数
返回值
成功 合法未用的键值
失败 -1
注意:
1,如果两个参数相同,那么产生的 key 值也相同。
2,第一个参数一般取进程所在的目录,因为在一个项目中需要通信的几个进程通常会出现在同一个目录当中。
3,如果同一个目录中的进程需要超过 1 个 IPC 对象,可以通过第二个参数来标识。
4,系统中只有一套 key 标识,也就是说,不同类型的 IPC 对象也不能重复
可以使用以下命令来查看或删除当前系统中的 IPC 对象:
-
查看消息队列:ipcs -q
-
查看共享内存:ipcs -m
-
查看信号量:ipcs -s
-
查看所有的 IPC 对象:ipcs -a
-
删除指定的消息队列:ipcrm -q MSG_ID 或者 ipcrm -Q msg_key
-
删除指定的共享内存:ipcrm -m SHM_ID 或者 ipcrm -M shm_key
-
删除指定的信号量:ipcrm -s SEM_ID 或者 ipcrm -S sem_key



