进程是一个独立的资源分配单元,不同进程之间相互独立。不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信( IPC:Inter Processes Communication )
示意图:
进程间通信的功能:
Linux 操作系统支持的主要进程间通信的通信机制:
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
编号 | 信号 | 作用 |
---|---|---|
1 | SIGHUP | 本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一 session 内的各个作业, 这时它们与控制终端不再关联。此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。 |
2 | SIGINT | 程序终止( interrupt )信号, 在用户键入 INTR 字符(通常是 Ctrl + C )时发出,用于通知前台进程组终止进程。 |
3 | SIGQUIT | 和 SIGINT 类似, 但由 QUIT 字符(通常是 Ctrl + / )来控制. 进程在因收到 SIGQUIT 退出时会产生 core 文件, 在这个意义上类似于一个程序错误信号。 |
4 | SIGILL | 执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。 |
5 | SIGTRAP | 由断点指令或其它 trap 指令产生. 由d ebugger 使用。 |
6 | SIGABRT | 调用 abort 函数生成的信号。 |
7 | SIGBUS | 非法地址, 包括内存地址对齐( alignment )出错。比如访问一个四个字长的整数, 但其地址不是 4 的倍数。它与 SIGSEGV 的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。 |
8 | SIGFPE | 在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。 |
9 | SIGKILL | 用来立即结束程序的运行。本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。 |
10 | SIGUSR1 | 留给用户使用 |
11 | SIGSEGV | 试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据。 |
12 | SIGUSR2 | 留给用户使用 |
13 | SIGPIPE | 管道破裂。这个信号通常在进程间通信产生,比如采用 FIFO (管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到 SIGPIPE 信号。此外用Socket 通信的两个进程,写进程在写 Socket 的时候,读进程已经终止。 |
14 | SIGALRM | 时钟定时信号, 计算的是实际的时间或时钟时间。alarm 函数使用该信号。 |
15 | SIGTERM | 程序结束( terminate )信号, 与 SIGKILL 不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell 命令 kill 缺省产生这个信号。如果进程终止不了,我们才会尝试 SIGKILL。 |
16 | SIGSTKFLT | Linux专用,数学协处理器的栈异常 |
17 | SIGCHLD | 子进程结束时, 父进程会收到这个信号。如果父进程没有处理这个信号,也没有等待( wait )子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸进程。这种情况我们应该避免(父进程或者忽略 SIGCHILD 信号,或者捕捉它,或者 wait 它派生的子进程,或者父进程先终止,这时子进程的终止自动由 init 进程来接管)。 |
18 | SIGCONT | 让一个停止( stopped )的进程继续执行。本信号不能被阻塞。可以用一个 handler 来让程序在由 stopped 状态变为继续执行时完成特定的工作。例如, 重新显示提示符。 |
19 | SIGSTOP | 停止( stopped )进程的执行。注意它和 terminate 以及 interrupt 的区别:该进程还未结束, 只是暂停执行。本信号不能被阻塞,处理或忽略。 |
20 | SIGTSTP | 停止进程的运行, 但该信号可以被处理和忽略。用户键入 SUSP 字符时(通常是 Ctrl + Z )发出这个信号。 |
21 | SIGTTIN | 当后台作业要从用户终端读数据时,该作业中的所有进程会收到 SIGTTIN 信号。缺省时这些进程会停止执行。 |
22 | SIGTTOU | 类似于 SIGTTIN,但在写终端(或修改终端模式)时收到。 |
23 | SIGURG | 有“紧急”数据或 out-of-band 数据到达 socket 时产生。 |
24 | SIGXCPU | 超过 CPU 时间资源限制。这个限制可以由 getrlimit/setrlimit 来读取/改变。 |
25 | SIGXFSZ | 当进程企图扩大文件以至于超过文件大小资源限制。 |
26 | SIGVTALRM | 虚拟时钟信号。类似于 SIGALRM,但是计算的是该进程占用的 CPU 时间。 |
27 | SIGPROF | 类似于 SIGALRM/SIGVTALRM,但包括该进程用的 CPU 时间以及系统调用的时间。 |
28 | SIGWINCH | 窗口大小改变时发出。 |
29 | SIGIO | 文件描述符准备就绪,可以开始进行输入/输出操作。 |
30 | SIGPWR | Power failure |
31 | SIGSYS | 非法的系统调用。 |
当用户按某些终端键时: 终端上按“Ctrl+c”组合键通常产生中断信号 SIGINT,终端上按“Ctrl+\”键通常产生中断信号SIGQUIT,终端上按“Ctrl+z”键通常产生中断信号 SIGSTOP 等。
硬件异常将产生信号:除数为 0,无效的内存访问等。这些情况通常由硬件检测到,并通知内核,然后内核产生适当的信号发送给相应的进程。
软件异常将产生信号:当检测到某种软件条件已发生,并将其通知有关进程时,产生信号。
调用函数将发送信号
运行 kill 命令将发送信号: 此程序实际上是使用 kill 函数来发送信号。也常用此命令终止一个失控的后台进程,例如使用kill -9 进程号可杀死程序。
kill函数:
#include <sys/types.h> #include <signal.h> int kill(pid_t pid, int sig);
功能:给指定进程发送指定信号
参数:
返回值:成功返回0 ,失败返回-1
示例代码kill.c:
#include <signal.h> #include <sys/types.h> #include <stdio.h> #include <stdlib.h> int main(int argc,char **argv) { int signum = atoi(argv[1]); int pid = atoi(argv[2]); //kill(pid,signum); char cmd[32] = {0}; sprintf(cmd,"kill -%d %d",signum,pid); system(cmd); return 0; }
signal函数:
#include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler);
功能:注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。
参数:
pause函数等待信号到来:
#include <unistd.h> int pause(void); /******************* 功能:等待信号的到来(此函数会阻塞)。将调用进程挂起直至捕捉到信号为止,此函数通常用于判断信号是否已到。 参数:无。 返回值:直到捕获到信号才返回 -1,且 errno 被设置成 EINTR。 ********************/
示例代码signal.c:
#include <signal.h> #include <stdio.h> #include <unistd.h> void handler(int signum) //信号处理函数 { printf("get signum:%d\n",signum); switch(signum){ case 2:printf("SIGINT\n");break; case 9:printf("SIGKILL\n");break; case 10:printf("SIGUSR1\n");break; } } int main() { signal(SIGINT,handler); signal(SIGKILL,handler);//KILL信号不能被处理,所以这个注册没意义,可以观察现象 signal(SIGUSR1,handler); pause(); //接收3个信号后正常退出 pause(); pause(); return 0; }
先执行signal.c,接收信号后的显示:
sigqueue函数:
#include <signal.h> union sigval { int sival_int; void *sival_ptr; }; int sigqueue(pid_t pid, int sig, const union sigval value);
功能:向指定进程发送一个信号和数据
参数:
返回值:若成功,返回 0;否则,返回 -1
示例代码sigqueue.c:
#include <signal.h> #include <stdio.h> #include <stdlib.h> int main(int argc,char **argv) { int signum = atoi(argv[1]); int pid = atoi(argv[2]); union sigval value; value.sival_int = 666; sigqueue(pid,signum,value); printf("done\n"); return 0; }
sigaction函数:
#include<signal.h> int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact); struct sigaction { void (*sa_handler)(int); //信号处理程序,同signal,不接受额外数据,SIG_IGN 为忽略,SIG_DFL 为默认动作 void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序,能够接受额外数据和sigqueue配合使用 sigset_t sa_mask;//阻塞关键字的信号集,可以再调用捕捉函数之前,把信号添加到信号阻塞字,信号捕捉函数返回之前恢复为原先的值。 int sa_flags;//影响信号的行为SA_SIGINFO表示能够接受数据 };//回调函数句柄sa_handler、sa_sigaction只能任选其一
功能:sigaction 可以用来查询或设置信号处理方式。
参数:
signum:参数指出要捕获的信号类型,也就是信号的编号
act:参数指定新的信号处理方式,struct sigaction类型如果不为空说明需要对该信号有新的配置
sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
sa_sigaction的参数有信号编号num,结构体siginfo,指针,指针为空表示没有数据,指针非空表示有数据,而struct siginfo这个结构体主要适用于记录接收信号的一些相关信息
siginfo的成员有很多si_signo 和 si_code 是必须实现的两个成员。可以通过这个结构体获取到信号的相关信息。关于发送过来的数据是存在两个地方的,sigval_t si_value这个成员中有保存了发送过来的信息;同时,在si_int或者si_ptr成员中也保存了对应的数据。
siginfo_t { int si_signo; /* Signal 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; /* Sending process ID */ 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; /* Signal value */ int si_int; /* POSIX.1b signal */ 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 */ int si_band; /* Band event */ int si_fd; /* File descriptor */ }
sa_flags 用来设置信号处理的其他相关操作,下列的数值可用:
SA_SIGINFO 提供附加信息,一个指向siginfo结构的指针以及一个指向进程上下文标识符的指针
SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号
oldact:备份,如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复
示例代码sigaction.c:
#include <signal.h> #include <stdio.h> #include <sys/types.h> #include <unistd.h> void handler(int signum, siginfo_t *info, void *text) { printf("signum:%d\n",signum); if(text != NULL){ printf("get data:%d\n",info->si_int); printf("sig from:%d\n",info->si_pid); } } int main() { struct sigaction act; act.sa_sigaction = handler; act.sa_flags = SA_SIGINFO; printf("my pid:%d\n",getpid()); sigaction(SIGUSR1,&act,NULL); pause(); return 0; }
编译执行sigaction.c(信号和数据来自sigqueue.c编译运行):
1、半双工,数据在同一时刻只能在一个方向上流动。
2、数据只能从管道的一端写入,从另一端读出。
3、写入管道中的数据遵循先入先出的规则。
4、管道所传送的数据是无格式的,这要求管道的读出方与写入方必须事先约定好数据的格式,如多少字节算一个消息等。
5、管道不是普通的文件,不属于某个文件系统,其只存在于内存中。
6、管道在内存中对应一个缓冲区。不同的系统其大小不一定相同。(一般默认64K)
7、从管道读数据是一次性操作,数据一旦被读走,它就从管道中被抛弃,释放空间以便写更多的数据。
8、管道没有名字,只能在具有公共祖先的进程(父进程与子进程,或者两个兄弟进程,具有亲缘关系)之间使用。(没关系的进程得不到管道的文件描述符,子进程的文件描述符是fork到的)
#include <unistd.h> int pipe(int pipefd[2]); /******************************************* 功能:创建无名管道。 参数: pipefd : 为 int 型数组的首地址,其存放了管道的文件描述符 pipefd[0]、pipefd[1]。 当一个管道建立时,它会创建两个文件描述符 fd[0] 和 fd[1]。其中 fd[0] 固定用于读管道,而 fd[1] 固定用于写管道。一般文件 I / O 的函数都可以用来操作管道(lseek() 除外)。 返回值: 成功返回0,失败返回-1 ************************************************/
示例代码pipe.c:
#include <stdio.h> #include <unistd.h> #include <string.h> //int pipe(int pipefd[2]); int main() { int fd[2]; int pid; char buf[32] = {0}; if(pipe(fd) == -1){ //创建管道并判断是否创建失败 printf("creat pipe fail\n"); } pid = fork(); //创建进程 if(pid < 0){ //如果创建进程失败 printf("creat pid fail\n"); } else if(pid > 0){ //进入父进程 printf("this is father\n"); close(fd[0]); //关闭读 write(fd[1],"wo nen die!",strlen("wo nen die!")); //往fd[1]写字符串 sleep(1); //等子进程退出 } else{ //进入子进程 printf("this is child\n"); close(fd[1]); //关闭写 read(fd[0],buf,128); //将父进程中写入的字符串从fd[0]中读出来 printf("read father %s\n",buf); //打印 } return 0; }
可以在终端使用shell指令mkfifo 文件名直接创建一个管道文件
使用函数创建:
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *pathname, mode_t mode);
open() 以只读方式打开 FIFO 时,要阻塞到某个进程为写而打开此 FIFO
open() 以只写方式打开 FIFO 时,要阻塞到某个进程为读而打开此 FIFO
发送mkfifo_w.c:
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> int main() { if(mkfifo("./named pipe",0600) == -1 && errno != EEXIST){ printf("creat named pipe failed\n"); perror("why"); } int fd = open("./named pipe",O_WRONLY); write(fd,"NB 666 My Baby!\n",strlen("NB 666 My Baby!\n")); close(fd); return 0; }
接收mkfifo_r.c:
#include <sys/types.h> #include <sys/stat.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <fcntl.h> #include <unistd.h> int main() { int i = 5; char buf[128] = {0}; if(mkfifo("./named pipe",0600) == -1 && errno != EEXIST){ printf("creat named pipe failed\n"); perror("why"); } int fd = open("./named pipe",O_RDONLY); while(i--){ read(fd,buf,128); printf("read from named pipe:\n%s",buf); sleep(1); } close(fd); return 0; }
接收端显示:
IPC 对象包含: 共享内存、消息队列和信号量
每个IPC对象有唯一的ID(IPC对象创建的时候由系统分配的一个数字,只有创建IPC对象的进程可以获得ID,别的进程不知道这个ID号)
IPC对象创建后一直存在,直到被显式地删除
每个IPC对象有一个关联的KEY(可以看成IPC对象的一个属性,通过KEY值,可以使不同的进程能够打开同一个IPC对象。创建IPC对象的进程把KEY值和IPC对象关联)
查看IPC对象 ipcs
iprm删除IPC对象
ipcrm [ -M key | -m id | -Q key | -q id | -S key | -s id ] ...
用ftok生成键值
#include<sys/types.h> #include<sys/ipc.h> key_t ftok(const char *pathname,int proj_id); /***************************** 函数功能:系统IPC键值的格式转换函数 函数参数:const char *pathname: 文件路径名 int proj_id: 子序号,虽然是int类型,但是只使用8bits 函数返回:成功:返回生成的key 出错:-1 ******************************/
消息队列是消息的链接表,存放在内核中,一个消息队列由一个标识符(队列ID)来标识
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
消息队列独立于发送与接收进程,进程终止时,消息队列中的内容不会被删除
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按照消息的类型读取
msgget函数:
#include <sys/msg.h> int msgget(key_t key, int msgflg);
功能:创建一个新的或打开一个已经存在的消息队列。不同的进程调用此函数,只要用相同的 key 值就能得到同一个消息队列的标识符。
参数:
返回值: 成功返回消息队列的标识符 ;失败返回-1
消息队列的数据格式由一个结构体定义,mtext的大小可由用户分配
struct _msg { long mtype; // 消息类型 char mtext[128]; // 消息正文 };
发送消息msgsnd:
#include <sys/msg.h> int msgsnd( int msqid, const void *msgp, size_t msgsz, int msgflg);
功能: 将新消息添加到消息队列。
参数:
返回值:成功0;失败-1
接收消息msgrcv:
#include <sys/msg.h> ssize_t msgrcv( int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg );
功能:从标识符为 msqid 的消息队列中接收一个消息。一旦接收消息成功,则消息在消息队列中被删除。
参数:
msqid:消息队列的标识符,代表要从哪个消息列中获取消息。
msgp: 存放消息结构体的地址。
msgsz:消息正文的字节数。
msgtyp:消息的类型。可以有以下几种类型:
msgflg:函数的控制属性。其取值如下:
返回值: 成功读取消息的长度; 失败:-1
控制消息队列对象msgctl:
#include <sys/ipc.h> #include <sys/msg.h> int msgctl(int msqid, int cmd, struct msqid_ds *buf);
A.c:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[128]; /* message data */ }; int main() { int msqid; key_t key = ftok(".",2021); struct msgbuf wrBuf = { 888,"How are you? ^_^ "}; struct msgbuf reBuf; msqid=msgget(key, IPC_CREAT|0777); if(msqid == -1){ printf("msgget error"); exit(1); } msgsnd(msqid,&wrBuf,strlen(wrBuf.mtext),0); msgrcv(msqid,&reBuf,sizeof(reBuf.mtext),999,0); printf("mtext from B:%s\n",reBuf.mtext); msgctl(msqid,IPC_RMID,NULL); return 0; }
B.c
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> #include <stdio.h> #include <stdlib.h> #include <string.h> struct msgbuf { long mtype; /* message type, must be > 0 */ char mtext[128]; /* message data */ }; int main() { int msqid; struct msgbuf reBuf; struct msgbuf wrBuf = {999,"666666!"}; key_t key = ftok(".",2021); msqid=msgget(key, IPC_CREAT|0777) ; if(msqid == -1){ perror("msgget error"); exit(1); } if(msgrcv(msqid,&reBuf,sizeof(reBuf.mtext),888,0) == -1){ perror("msgrcv error"); }else{ printf("mtext from A:%s\n",reBuf.mtext); } if(msgsnd(msqid,&wrBuf,strlen(wrBuf.mtext),0) == -1){ perror("msgsnd error"); } if(msgctl(msqid,IPC_RMID,NULL) == -1){ perror("msgctl error"); } return 0; }
显示
B进程: mtext from A:How are you? ^_^ A进程: mtext from B:666666!
1、创建或者打开共享内存
2、进程A连接(映射)共享内存,写入数据
3、进程A断开
4、进程B连接(映射)共享内存,读取数据
5、进程B断开
6、释放共享内存
shmget函数:
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size,int shmflg);
shmat函数:
#include <sys/types.h> #include <sys/shm.h> void *shmat(int shmid, const void *shmaddr, int shmflg);
功能: 将一个共享内存段映射到调用进程的数据段中
参数:
返回值: 成功返回共享内存段映射地址( 相当于这个指针就指向此共享内存 ) ;失败返回-1
shmdt函数:
#include <sys/types.h> #include <sys/shm.h> int shmdt(const void *shmaddr);
功能: 将共享内存和当前进程分离
参数:
返回值: 成功0 ;失败-1
shmct函数:
#include <sys/ipc.h> #include <sys/shm.h> int shmctl(int shmid, int cmd, struct shmid_ds *buf);
shmid:共享内存标识符。
cmd:函数功能的控制,其取值如下:
buf:shmid_ds 数据类型的地址,用来存放或修改共享内存的属性。
返回值: 成功0 ;失败-1
本例程未使用信号量,写端停留5秒等待读端读取信息,配合信号量使用的代码见文末
写进程shm_w.c
#include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <string.h> #include <stdlib.h> int main() { int shmid; //共享内存标识符 char *shmaddr; key_t key; key = ftok(".",1); //获取键值 shmid = shmget(key,1024*4,IPC_CREAT|0666); //打开或者创建共享内存 if(shmid == -1){ printf("shmget NO OK\n"); exit(-1); } shmaddr = shmat(shmid,0,0); //共享内存连接到当前进程的地址空间 printf("shmat ok\n"); strcpy(shmaddr,"hello world"); //向内存中写入数据 sleep(5); //睡眠5秒,等待内存数据被读走 shmdt(shmaddr); //断开进程和内存的连接 shmctl(shmid,IPC_RMID,0); //删除共享内存段 printf("quit\n"); return 0; }
读进程shm_r.c
#include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <string.h> #include <stdlib.h> int main() { int shmid; char *shmaddr; key_t key; key = ftok(".",1); //获取键值 shmid = shmget(key,1024*4,0); //打开创建的共享内存,获取内存ID, if(shmid == -1){ printf("shmget no ok\n"); exit(-1); } shmaddr = shmat(shmid,0,0); //共享内存连接到当前进程的地址空间 printf("shmat ok\n"); //表示连接成功 printf("data : %s",shmaddr); //将内存地址中的数据读出,打印 shmdt(shmaddr); //断开内存和当前进程的连接 printf("quit\n"); return 0; }
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源(比如上文说的共享内存)的访问。
信号量可以用来保证两个或多个关键代码段不被并发调用。
在实际应用中两个进程间通信可能会使用多个信号量,因此 System V 的信号量以集合的概念来管理。
信号量集合数据结构:struct semid_ds,此数据结构中定义了整个信号量集的基本属性。
/* Obsolete, used only for backwards compatibility and libc5 compiles */ struct semid_ds { struct ipc_perm sem_perm; /* permissions .. see ipc.h */ __kernel_time_t sem_otime; /* last semop time */ __kernel_time_t sem_ctime; /* last change time */ struct sem *sem_base; /* ptr to first semaphore in array */ struct sem_queue *sem_pending; /* pending operations to be processed */ struct sem_queue **sem_pending_last; /* last pending operation */ struct sem_undo *undo; /* undo requests on this array */ unsigned short sem_nsems; /* no. of semaphores in array */ };
信号量数据结构:struct sem,此数据结构中定义了信号量的基本属性。
/* One semaphore structure for each semaphore in the system. */ struct sem { int semval; /* current value *信号量的值*/ int sempid; /* pid of last operation *最后一个操作信号量的进程号*/ struct list_head sem_pending; /* pending single-sop operations */ };
编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。
PV 原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。(P表示通过,V表示释放)
信号量用于互斥:
信号量用于同步:
创建信号量数组
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int semflg);
功能: 创建或打开一个信号量集合,该集合中可以包含多个信号量。
参数:
返回值: 成功返回信号量集标识符;失败返回 -1
semop函数对信号量组进行操作
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semop(int semid, struct sembuf *sops, size_t nsops); struct sembuf{ unsigned short sem_num; /*信号量的序号*/ short sem_op; /*信号量的操作值*/ short sem_flg; /*信号量的操作标识*/ };
功能: 操作信号量,主要进行信号量加减操作。
参数:
结构体成员使用说明如下:
sem_num:信号量集中信号量的序号
sem_op 取值如下:
sem_flag 取值如下:
返回值: 成功0 ;失败-1
semctl函数控制信号量的相关信息
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semctl(int semid, int semnum, int cmd, ...); union semun{ int val; /*信号量的值*/ struct semid_ds *buf; /*信号量集合信息*/ unsigned short *array;/*信号量值的数组*/ struct seminfo *__buf;/*信号量限制信息*/ };
功能: 对信号量集合以及集合中的信号量进行操作。
参数:
GETVAL:获取信号量的值。此时函数有3个参数。semctl() 函数的返回值即为信号量的值
SETVAL:设置信号量的值。此时函数有4个参数。第4个参数为联合体中的val,其值为信号量的值。
IPC_STAT:获取信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf
IPC_SET:设置信号量集合的信息。此时函数有4个参数。第4个参数为联合体中的__buf
IPC_RMID:删除信号量集。此时函数有3个参数,第2个参数semnum不起作用
GETALL:获取所有信号量的值。此时函数有4个参数,第2个参数semnum不起作用。第4个参数为联合体中的array,其值为用来存放所有信号量值的数组的首地址。
SETALL:设置所有信号量的值 ,参数说明同上
IPC_INFO:获取信号量集合的限制信息。此时函数有4个参数,第2个参数semnum不起作用。
GETPID:获取信号的进程号,即最后操作信号量的进程。此时函数有3个参数。 semctl() 函数的返回值即为信号的进程号。
GETNCNT:获取等待信号的值递增的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。
GETZCNT:获取等待信号的值递减的进程数。此时函数有3个参数。semctl() 函数的返回值即为进程数。
返回值: 成功0 ;失败-1
下例将sem设为0,父进程使用P操作会等待子进程V操作释放
#include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #include <stdio.h> #include <unistd.h> 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) { struct sembuf set; set.sem_num = 0; set.sem_op = -1; set.sem_flg = SEM_UNDO; semop(semid, &set,1); printf("get key\n"); } void vPutKey(int semid) { struct sembuf set; set.sem_num = 0; set.sem_op = 1; set.sem_flg = SEM_UNDO; semop(semid, &set,1); printf("put key\n"); } int main() { key_t key = ftok(".",2); int semid = semget(key,1,IPC_CREAT|0666); union semun seminit; seminit.val = 0; semctl(semid,0,SETVAL,seminit); int pid = fork(); if(pid > 0){ pGetKey(semid); printf("this is father\n"); vPutKey(semid); semctl(semid,0,IPC_RMID); }else if(pid == 0){ printf("this is child\n"); sleep(5); vPutKey(semid); }else{ printf("fork error!\n"); } return 0; }
参考信号量的同步模型,建立两个信号量,一个为0,另一个为1
写进程shm_w.c:
#include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <stdio.h> #include <string.h> #include <stdlib.h> 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 P(int semid,unsigned short num) { struct sembuf set; set.sem_num = num; //信号量在数组里的序号 set.sem_op = -1; //信号量的操作值 set.sem_flg = SEM_UNDO; //信号量的操作标识 semop(semid, &set,1); printf("get key\n"); } void V(int semid,unsigned short num) { struct sembuf set; set.sem_num = num; set.sem_op = 1; set.sem_flg = SEM_UNDO; semop(semid, &set,1); printf("put key\n"); } int main() { key_t key1,key2; key1 = ftok(".",1); //获取键值 key2 = ftok(".",2); int shmid = shmget(key1,1024*4,IPC_CREAT|0666); //打开或者创建共享内存 int semid = semget(key2,2,IPC_CREAT|0666);//打开或者创建信号量组 union semun seminit; //信号量初始化 seminit.val = 1; //第一个信号量设置为1 semctl(semid,0,SETVAL,seminit); seminit.val = 0;//第二个信号量设置为0 semctl(semid,1,SETVAL,seminit); P(semid,0); //给第一个信号量上锁 char *shmaddr = shmat(shmid,0,0); //共享内存连接到当前进程的地址空间 printf("shmat ok\n"); strcpy(shmaddr,"hello world"); //向内存中写入数据 shmdt(shmaddr); //断开进程和内存的连接 V(semid,1); //释放第二个信号量 P(semid,0); //等待第一个信号量的释放 shmctl(shmid,IPC_RMID,0); //删除共享内存段 semctl(semid,0,IPC_RMID); //删除信号量组 printf("quit\n"); return 0; }
读进程shm_r.c:
#include <sys/ipc.h> #include <sys/shm.h> #include <sys/sem.h> #include <stdio.h> #include <string.h> #include <stdlib.h> 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 P(int semid,unsigned short num) { struct sembuf set; set.sem_num = num; //信号量在数组里的序号 set.sem_op = -1; //信号量的操作值 set.sem_flg = SEM_UNDO; //信号量的操作标识 semop(semid, &set,1); printf("get key\n"); } void V(int semid,unsigned short num) { struct sembuf set; set.sem_num = num; set.sem_op = 1; set.sem_flg = SEM_UNDO; semop(semid, &set,1); printf("put key\n"); } int main() { key_t key1,key2; key1 = ftok(".",1); //获取键值 key2 = ftok(".",2); int shmid = shmget(key1,1024*4,IPC_CREAT|0666); //打开或者创建共享内存 int semid = semget(key2,1,IPC_CREAT|0666);//打开或者创建信号量组 P(semid,1); //等待第二个信号量释放 char *shmaddr = shmat(shmid,0,0); //共享内存连接到当前进程的地址空间 printf("shmat ok\n"); //表示连接成功 printf("data : %s\n",shmaddr); //将内存地址中的数据读出,打印 shmdt(shmaddr); //断开内存和当前进程的连接 V(semid,0); //释放第一个信号量 printf("quit\n"); return 0; }
因为信号量的初始化在写端,所以先执行写端
终端显示:
写端:
读端: