Linux进程间的通信方式

本文详细介绍了Linux中的五种进程间通信(IPC)机制:管道(包括无名管道和命名管道FIFO)、消息队列、共享内存、信号以及信号量。每种机制的特点、使用场景和关键函数进行了深入探讨,例如管道的半双工特性、FIFO的文件系统关联、消息队列的随机查询能力、共享内存的高效数据交换以及信号量的同步功能。通过实例代码展示了如何在实际编程中应用这些机制。
摘要由CSDN通过智能技术生成

一、前言

什么是进程间通信,就是不同进程之间相互通信(传播和信息交换),简称:IPC(InterProcess Communication)。

二、进程间通信方式:

常用的七种:

  1. 管道 ——内存
  2. FIFO ——文件系统
  3. 消息队列 ——内核
  4. 信号量
  5. 共享存储
  6. UNXI域套接字 (Socket)
  7. Stream(流)

注:(其中socket和stream支持不同主机上的两个进程IPC)套接字也是一种进程间通信机制,与其他通信机制不同的是,除了本机的进程间通信, 它也可用于不同机器间的进程通信。用于本机器的进程间通信时,成为本地套接字,用于不同机器之间的 进程间通信时,称为网络套接字。

1、管道(无名管道)

管道是由调用pipe函数而创建的。

特点:

  1. 它们是半双工的。数据只能在一个方向上流动
  2. 它只能在具有公共祖先的进程之间通信(父子进程或者兄弟进程)。通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
  3. 它可以看成特殊文件,它不是普通文件,不属于任何其他文件系统,且只存在内存中。

原型:

#include <unistd.h>
int pipe(int fd[2]);

Returns: 0 if OK, −1 on error

代码:demo1.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
        int fd[2];//fd[0],fd[1]分别为读写,简单记忆:0 1 读写
        char buf[100];
        int pid;
//      管道是由调用pipe函数而创建的,
//      pipe() funtion before create PID >>>ERROR(fail)
        if(pipe(fd) == -1){
                printf("Program running error\n");
                exit(-1);
        }
        pid = fork();
        if(pid < 0){
                printf("Found PID Fail\n");
                exit(-1);
        }
        else if(pid > 0){
                printf("This is father PID\n");
                close(fd[0]);
                write(fd[1],"Hello you are very handsome",64);
                wait(NULL);
        }
        else{
                printf("This is child PID\n");
                close(fd[1]);
                read(fd[0],buf,101);
                printf("read buf:%s\n",buf);
                exit(0);
        }
        return 0;
}
注:
当read读不到数据,阻塞在哪里,等到父进程写入数据之后在哪里等待wait,
等待子进程read读到数据,打印,退出exit。

简单的理解就是管道为单通道:
进行读时,写关闭;
进行写时,读关闭;
管道的数据被读走就没有了,读数据的是read(),读不到数据会阻塞在哪里

运行结果:

This is father PID
This is child PID
read buf:Hello you are very handsome

2、FIFO (也被称为命名管道

管道只能由相关进程使用,它们共同的祖先进程创建了管道。但是,通过FIFO,不相关的进程也能交换数据。FIFO是一种文件类型。stst结构成员st_mode的编码指明文件是否是FIFO类型。可以用S_ISFIFO宏对此进行测试.

特点
(1) FIFO可以在无关的进程之间交换数据,与无名管道不同
(2) FIFO有路径名与之相关联,它以一种特殊的设备文件形式存在于系统中

原型

#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);

Both return: 0 if OK, −1 on error

代码

读的代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>

int main()
{
        char buf[64] = {0};
        //mode之前被我写成0060,导致出现的严重的error,调试了好久
        //当前文件夹下创建一个有名管道:file
        if( ( mkfifo("./file",0600) == -1 ) && errno != EEXIST ){
                printf("mkfifo failure\n");
                perror("why");//打印失败的原因
        }
        int fd = open("./file",O_RDONLY);//打开我们创建的管道,读取的方式
        //默认以阻塞的方式打开。
        //要以非阻塞的方式打开mode:O_RDONLY|O_NONBLOCK

        int n_read=read(fd,buf,30);

        printf("read %d byt from FIFO,read content:%s\n",n_read,buf);

        close(fd);
        return 0;
}
注:
#include <errno.h> 

#define    EPERM         1        /* Operation not permitted */
#define    ENOENT         2        /* No such file or directory */
#define    ESRCH          3        /* No such process */
#define    EINTR          4        /* Interrupted system call */
#define    EIO            5        /* Input/output error */
#define    ENXIO          6        /* Device not configured */
#define    E2BIG          7        /* Argument list too long */
#define    ENOEXEC        8        /* Exec format error */
#define    EBADF          9        /* Bad file descriptor */
#define    ECHILD         10        /* No child processes */
#define    EDEADLK        11        /* Resource deadlock avoided */

写的代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h>
int main()
{
        char *str = "You are smart,from FIFO";
        int fd = open("./file",O_WRONLY);//写的方式打开

        int n_write=write(fd,str,strlen(str));

        if(n_write == strlen(str)){
                printf("Write FIFO SUCCER\n");
        }
        close(fd);
        return 0;
}
~  

运行结果:

> CLC@Embed_Learn:~/LinuxLearn/IPC$ ./rd
> 
> 
//进程阻塞在这里,知道遇到其他进程的操作(写)
>  CLC@Embed_Learn:~/LinuxLearn/IPC$ ./wr
> Write FIFO SUCCER
> 
//上面./rd进程运行,开始
> CLC@Embed_Learn:~/LinuxLearn/IPC$ ./rd
> Read 23 byt from FIFO,read content:You are smart,from FIFO

3、消息队列

0

  • 是消息的链接表 ,存放在内核中,并由消息队列标识符标识。
  • 将称消息队列为“队列”,其标识符为“队列ID”。

特点:

  1. 可以把消息看作一个记录,具有特定的格式以及特定的优先级。
  2. 对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息;
  3. 对消息队列有读权限的进程则可以从消息队列中读走消息。
  4. 消息队列是随内核持续的。
  5. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除
  6. 消息队列可以实现消息的随机查询,消息不一定要先出先进的顺序读取,也可以按照消息的类型读取。

函数

  • msgget用于创建一个新队列或打开一个现存的队列。
  • msgsnd用于将新消息添加到队列尾端。msgsnd,每个消息包含一个正长整型类型字段,一个非负长度以及实际数据字节(对应于长度),所有这些都在将消息添加到队列时,传送msgsnd。
  • msgrcv用于从队列中取消息。我们并不一定要以先进先出次序取消息,也可以按消息的类型字段取消息。

原型

(1)调用的第一个函数通常是msgget,其功能是打开一个现存队列或创建一个新队列。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int flag) ;

在这里插入图片描述

执行返回:若成功则为消息队列ID,若出错则为- 1

(2)msgctl函数对队列执行多种操作

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int  msqid, int cmd, struct msqid_ds *buf) ;

执行返回:若成功则为0,出错则为- 1

(3)调用msgsnd将数据放到消息队列上。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int  msqid, const void *ptr, size_t n bytes, int flag) ;

struct mymesg {
	long mtype; /* positive message type */
	char mtext[512]; /* message data ,of length n bytes* /
} ;
//ptr就是一个指向mymesg结构的指针
//flag的值可以指定为IPC_NOWAIT

返回:若成功则为0,若出错则为- 1

(4)msgrcv从队列中取用消息

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int  msqid, void * ptr, size_t n bytes, long type, int flag) ;

执行返回:若成功则为消息数据部分的长度,若出错则为 - 1

实现的功能

一边发数据,一边接收数据。
完了,
一边收数据,一边发数据。
同时完成一个进程发\收数据

代码:msgGet1.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
// int msgget(key_t key, int msgflg);
//int msgctl(int msqid, int cmd, struct msqid_ds *buf);

struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};
int main()
{
        
        key_t key;
        //获取key
        key=ftok(".",'q');//当前文件目录、//id:1-255随便,两边key要相等
        printf("ID:%x\n",key); //打印key以16进制
        //get msg(从内核获取队列)
        int msgID=msgget(key,IPC_CREAT|0777);//IPC_CREAT|0777创建可读可行
        printf("msgID=%d\n",msgID);
        if(msgID == -1){
                printf("creat msg failure\n");
        }
        struct msgbuf readBuf;
        //读取的地址 //多少字节 //888与接收的一样 //0非阻塞的方式        
        msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),888,0);
        printf("msgget read data:%s\n",readBuf.mtext);
        
        struct msgbuf sendBuf={889,"He love system programme"};
        msgsnd(msgID,&sendBuf,sizeof(sendBuf.mtext),0);
        msgctl(key,IPC_RMID,NULL);//删除队列
        
        return 0;

}

代码2:msgSend1.c

include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>

struct msgbuf {
               long mtype;       /* message type, must be > 0 */
               char mtext[128];    /* message data */
};


int main()
{
        struct msgbuf sendBuf={888,"I love system programme"};
        key_t key;
        key=ftok(".",'q');
        printf("ID:%x\n",key);
        int msgID=msgget(key,IPC_CREAT|0777);

        //get msint msgID=msgget(0x1111,IPC_CREAT | 0777);
        if(msgID == -1){
                printf("creat msg failure\n");
        }
        msgsnd(msgID,&sendBuf,sizeof(sendBuf.mtext),0);

        struct msgbuf readBuf;
        msgrcv(msgID,&readBuf,sizeof(readBuf.mtext),889,0);
        printf("msgget read data:%s\n",readBuf.mtext);
        msgctl(key,IPC_RMID,NULL);

        return 0;

}

运行的结果:

CLC@Embed_Learn:~/LinuxLearn/IPC$ ./snd
//会阻塞
//当另一个进程执行时,才会出现下面的结果
ID:7105645a
msgget read data:He love system programme

CLC@Embed_Learn:~/LinuxLearn/IPC$ ./get
ID:7105645a
msgID=65538
msgget read data:I love system programme

4、共享内存 (Shared Memory)

允许两个或者多个进程共享一个给定的存储区

特点

  1. 因为数据不需要在客户机和服务器之间复制,所以这是最快的一种 IPC。(进程是直接对内存进行存取)
  2. 使用共享存储的唯一窍门是多个进程之间对一给定存储区的同步存取。
  3. 信号量共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

原型
(1) shmget,它获得一个共享存储标识符(shmid)

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h> 
int shmget(key_t key, int size , int flag) ;

返回:若成功则为共享内存ID,若出错则为- 1

(2) shmctl , 函数对共享存储段执行多种操作

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h> int shmctl(int  shmid, int cmd, struct shmid_ds * buf) ;

返回:若成功则为0,若出错则为- 1

(3)shmat,一旦创建了一个共享存储段,进程就可调用shmat将其连接到它的地址空间中

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
void *shmat(int  shemid, void * addr, int flag) ;

返回:若成功则为指向共享存储段的指针,若出错则为 - 1

(4) shmdt,当对共享存储段的操作已经结束时,则调用shmdt脱接该段(:注意,这并不从系统中删除其标识符以及其数据结构。该标识符仍然存在,直至某个进程(一般是服务器)调用 shmctl带命令IPC_RMID)特地删除它)

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int shmdt(void * addr) ;
//注:addr参数是以前调用shmat时的返回值

返回:若成功则为0,若出错则为- 1

编程思路:
2

代码:
code one:shrw.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//       int shmdt(const void *shmaddr);
//       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
int main()
{
        key_t key;
        key=ftok(".",1);
        if(key == -1){
                printf("key error\n");
                exit(-1);
        }
        //1.get share memory
        int shmid=shmget(key,1024*2,IPC_CREAT|0666);
        				//size:1024*n,n为整数 //shmflg
        if(shmid == -1){
                printf("Creat shm failure\n");
                exit(-1);
        }
        //2.map
        char *shmaddr;
        				//0(自动安排内存)//0(默认可读可写)
        shmaddr=shmat(shmid,0,0);//映射到shmaddr中
        //3.data interchange
        strcpy(shmaddr,"Touch share memory is successful ");
//      sleep(5);
        //4.free share memory
        shmdt(shmaddr);
        //5.dalete 
//      shmctl(shmid,IPC_RMID,0);
        return 0;
}

code two : shmr.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//       int shmdt(const void *shmaddr);
//       int shmctl(int shmid, int cmd, struct shmid_ds *buf);

int main()
{
        key_t key;
        key=ftok(".",1);
        if(key == -1){
                printf("get key error\n");
                exit(-1);
        }
        //1.get share memory
        							//0(默认可读可写)
        int shmid=shmget(key,1024*2,0);
        if(shmid == -1){
                printf("Creat shm failure\n");
                exit(-1);
        }
        //2.map
        char *shmaddr;
        shmaddr=shmat(shmid,0,0);
        //3.data interchange
        printf("share data is : %s\n",shmaddr);
        //4.free share memory
        shmdt(shmaddr);
        //5.dalete PID
        shmctl(shmid,IPC_RMID,0);
        return 0;
}

运行结果:

CLC@Embed_Learn:~/LinuxLearn/IPC$ ./wr
CLC@Embed_Learn:~/LinuxLearn/IPC$ ./rd
share data is : Touch share memory is successful 0

改进:
shme2.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//       int shmdt(const void *shmaddr);
//       int shmctl(int shmid, int cmd, struct shmid_ds *buf);

int main()
{
	key_t key,key1;
	key = ftok(".",1);
	key1 = ftok(".",2);
	if(key == -1){
		printf("key error\n");
		exit(-1);
	}
	//1.get share memory
	int shmid=shmget(key,1024*2,IPC_CREAT|0666);
	int shmid1=shmget(key1,1024*2,0666);
	
	if(shmid == -1){
		printf("Creat shm failure\n");
		exit(-1);
	}
	//2.map
	
	char *shmaddr;
	char *shmaddr1;
	shmaddr=shmat(shmid,0,0);
	shmaddr1=shmat(shmid1,0,0);
	//3.data interchange
	strcpy(shmaddr,"Touch share memory is successful 0");
	printf("data:%s\n",shmaddr1);
//	sleep(5);
	//4.free share memory

	shmdt(shmaddr);
	shmdt(shmaddr1);
	//5.dalete 
	shmctl(shmid1,IPC_RMID,0);
	return 0;
}

shmr2.c

#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
//int shmget(key_t key, size_t size, int shmflg);
//void *shmat(int shmid, const void *shmaddr, int shmflg);
//       int shmdt(const void *shmaddr);
//       int shmctl(int shmid, int cmd, struct shmid_ds *buf);

int main()
{
	key_t key,key1;
	key=ftok(".",1);
	key1=ftok(".",2);
	if(key == -1){
		printf("get key error\n");
		exit(-1);
	}
	//1.get share memory
	int shmid=shmget(key,1024*2,0666);
	int shmid1=shmget(key1,1024*2,IPC_CREAT|0666);
	if(shmid == -1){
		printf("Creat shm failure\n");
		exit(-1);
	}
	//2.map
	char *shmaddr;
	char *shmaddr1;
	shmaddr=shmat(shmid,0,0);
	shmaddr1=shmat(shmid1,0,0);

	//3.data interchange
	strcpy(shmaddr1,"Touch share memory is successful 1");
	printf("share data is : %s\n",shmaddr);
	//4.free share memory
	shmdt(shmaddr);
	shmdt(shmaddr1);
	//5.dalete 
	shmctl(shmid,IPC_RMID,0);
	return 0;
}

运行结果:
在这里插入图片描述

小结

  • 指令:ipcs -m (有哪些共享内存,是在没有使用shmctl()函数,干掉shm的情况)
------ Shared Memory Segments --------
key        shmid      owner      perms      bytes      nattch     status      
0x0105645a 196608     CLC        666        2048       0                       
  • 指令:ipcs -m shmid(删除shm)
    (ipcs -m 196608)

5、信号(signal)

信号是软件中断
信号提供了一种处理异步事件的方法:终端用户键入中断键,则会通过信号机构停止一个程序。如,Linux终端用户输入了 ctrl+c 来中断进程,会通过信号机制停止一个程序。

信号的函数(SIG)
指令:kill -l

CLC@Embed_Learn:~/LinuxLearn/IPC$ 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

详情可查:
指令: man 7 signal

信号的处理(特点),有以下三种方式:

  1. 忽略此信号。大多数信号都可使用这种方式进行处理,但有两种信号却决不能被忽略。它们是:SIGKILLSIGSOP,这两种信号不能被忽略的原因是:它们向超级用户提供一种使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号(例如非法存储访问或除以0),则进程的行为是未定义的.
  2. 捕捉信号。为了做到这一点要通知内核在某种信号发生时,调用一个用户函数。在用户函数中,可执行用户希望对这种事件进行的处理。例如,若编写一个命令解释器,当用户用键盘产生中断信号时,很可能希望返回到程序的主循环,终止系统正在为该用户执行的命令。如果捕捉到SIGCHLD信号,则表示子进程已经终止,所以此信号的捕捉函数可以调用 waitpid以取得该子进程的进程ID以及它的终止状态。又例如,如果进程创建了临时文件,那么可能要为SIGTERM信号编写一个信号捕捉函数以清除临时文件( kil l命令传送的系统默认信号是终止信号)。
  3. 执行系统默认动作。给出(kill -l)中的一种信号的系统默认动作。注意,对大多数信号的系统默认动作是终止该进程。
入门级——signal函数

原型:

 #include <signal.h>
 typedef void (*sighandler_t)(int);
 sighandler_t signal(int signum, sighandler_t handler);
 //返回:成功则为以前的信号处理配置,若出错则为 SIG_ERR


signal函数的原型说明此函数要求两个参数,返回一个函数指针,而该指针所指向的函数无返回值(void)。第一个参数signo是一个整型数,第二个参数是函数指针,它所指向的函数需要一个整型参数,无返回值。用一般语言来描述也就是要向信号处理程序传送一个整型参数,而它却无返回值。当调用signal设置信号处理程序时,第二个参数是指向该函数 (也就是信号处理程序)的指针。signal的返回值则是指向以前的信号处理程序的指针

code :sigDemo2.c

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

//   typedef void (*sighandler_t)(int);
//   sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{
        printf("signum:%d\n",signum);
        printf("quit cover!!\n");
        switch (signum) {
                case 2:
                        printf("signal=%d\n",signum);
                        break;
                case 1:
                        printf("signal=%d\n",signum);
                        break;
                case 10:
                        printf("signal=%d\n",signum);
                        break;
        }
}

int main()
{
        printf("pid=%d\n",getpid());
        signal(SIGINT,handler);
        signal(SIGUSR1,handler);
        signal(SIGHUP,handler);
        while(1);
        return 0;
}

运行结果:

//需要等待发来信号
CLC@Embed_Learn:~/LinuxLearn/IPC$ gcc signalDemo2.c
CLC@Embed_Learn:~/LinuxLearn/IPC$ ./a.out
pid=11180
signum:2
quit cover!!
signal=2
signum:1
quit cover!!
signal=1
signum:10
quit cover!!
signal=10
Killed
//发过去的信号(这一部分还可以由code 2的进程来执行完成)
CLC@Embed_Learn:~/LinuxLearn/IPC$ kill -2 11180
CLC@Embed_Learn:~/LinuxLearn/IPC$ kill -1 11180
CLC@Embed_Learn:~/LinuxLearn/IPC$ kill -10 11180
CLC@Embed_Learn:~/LinuxLearn/IPC$ kill -9 11180

code 2:

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
int main(int agrc,char **argv)
{
        int signum;
        int pid;
        char cmd[32]={0};
        signum=atoi(argv[1]); //atoi将argv(1)转化问整型
        pid=atoi(argv[2]);
        printf("signum=%d,pid=%d\n",signum,pid);
//       kill(signum,pid);
        sprintf(cmd,"kill -%d %d",signum,pid);//将kill -%d %d 赋值给cmd
        system(cmd);
        printf("successful Over!!!\n");
        return 0;
}

CLC@Embed_Learn:~/LinuxLearn/IPC$ gcc sigDemo3.c
CLC@Embed_Learn:~/LinuxLearn/IPC$ ./a.out 10 11847
signum=10,pid=11847
successful Over!!!

高级——sigaction函数
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
   void       (*sa_handler)(int); //信号处理程序,不接受额外数据,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只能任选其一

指令man sigaction来查看。

sigaction 是一个系统调用,根据这个函数原型,可以看出在函数原型中,第一个参数signum应该就是注册的信号的编号;第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。

信号发送函数——高级版

#include <signal.h>
int sigqueue(pid_t pid, int sig, const union sigval value);
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

sigaction函数的讲解
01

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 */
               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) */
           }

code:signalRevecive.c

#include <signal.h>
#include <stdio.h>

//int sigaction(int signum, const struct sigaction *act,
//              struct sigaction *oldact);
void handler(int signum,siginfo_t *info,void *context)
{
        printf("get signum:%d\n",signum);
        if(context != NULL){
                printf("get data=%d\n",info->si_int);
                printf("get data=%d\n",info->si_value.sival_int);
        }
}


int main(int agrc,char *agrv[])
{
        struct sigaction act;
        act.sa_sigaction =handler;
        act.sa_flags=SA_SIGINFO;
		int signum = atoi(agrv[1]);
        printf("getpid %d\n",getpid());
        sigaction(signum,&act,NULL;
        while(1);//死循环在这里,不然程序不能接收到信号,就会马上退出
        return 0;
}

code:signalSend.c

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

//int sigqueue(pid_t pid, int sig, const union sigval value);

int main(int agrc,char **agrv)
{
        int signum;
        int pid;
        signum=atoi(agrv[1]);
        pid=atoi(agrv[2]);
        printf("signum=%d,pid=%d\n",signum,pid);

        union sigval value;
        value.sival_int = 100;
        sigqueue(pid, signum,value);
        printf("%d\n",getpid());
        printf("Over\n");
        return 0;
}
                       

运行结果:
在这里插入图片描述

6、信号量(semaphore)——pv操作

信号量与已经介绍过的IPC机构(管道、FIFO以及消息列队)不同。它是一个计数器(不涉及数据,管理临界资源),用于多进程对共享数据对象的存取。为了获得共享资源,进程需要执行下列操作:

  1. 测试控制该资源的信号量
  2. 若此信号量的值为正,则进程可以使用该资源。进程将信号量值减 1,表示它使用了一个资源单位。
  3. 若此信号量的值为 0,则进程进入睡眠状态,直至信号量值大于 0。若进程被唤醒后,它返回至(第( 1 )步)

当进程不再使用由一个信息量控制的共享资源时,该信号量值增 1。如果有进程正在睡眠等待此信号量,则唤醒它们。
为了正确地实现信息量,信号量值的测试及减 1操作应当是原子操作。为此,信号量通常是在内核中实现的。
常用的信号量形式被称之为双态信号量(binary semaphore)。它控制单个资源,其初始值为1。
但是,一般而言,信号量的初值可以是任一正值,该值说明有多少个共享资源单位可供共享应用。
不幸的是,系统V的信号量与此相比要复杂得多。三种特性造成了这种并非必要的复杂性:

  1. 信号量并非是一个非负值,而必需将信号量定义为含有一个或多个信号量值的集合。当创建一个信号量时,要指定该集合中的各个值
  2. 创建信息量(semget)与对其赋初值(semctl)分开。这是一个致命的弱点,因为不能原子地创建一个信号量集合,并且对该集合中的所有值赋初值。
  3. 即使没有进程正在使用各种形式的系统V IPC,它们仍然是存在的,所以不得不为这种程序担心,它在终止时并没有释放已经分配给它的信号量。

特点:

  • 信号量用与于进程间的同步,若要在进程间传递数据需要结合贡献内存。
  • 信号量基于操作系统的P/V操作,程序对信号量的操作都是原子操作。
  • 每次对信号量的P/V操在这里插入代码片作不仅限于对信号量值增加或减少1,而且可以加减任意正整数,
  • 支持信号量组

原型:

  1. 要调用的第一个函数是semget以获得一个信号量ID
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int flag) ;
返回:若成功则返回信号量ID,若出错则为- 1
  1. semctl函数包含了多种信号量操作。(控制信号量的相关信息)
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, union semun arg) ;

注意,最后一个参数是个联合(union),而非指向一个联合的指针。
union semun {
int val ; /* for SETVAL */
struct semid_ds * buf ; /* for IPC_STAT and IPC_SET */
ushort * array ; /* for GETALL and SETALL */
} ;
  1. 函数semop自动执行信号量集合上的操作数组。
int semop(int semid,struct sembuf semoparray[], size_t nops);
返回:若成功则为0,若出错则为- 1

semoparray是一个指针,它指向一个信号量操作数组。
struct sembuf {
ushort sem_num ; /* member # in set (0, 1, ⋯, nsems-1 */
short sem_op ; /* operation(negative, 0,or pasitive */)
short sem_flg ; /* IPC_NOWAIT, SEM_UNDO */
} ;

code:sem2.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
//nems,信号量集的个数,设置为1个
//       int semget(key_t key, int nsems, int semflg);
//semnum操作第0个信号量,相当于数据都是从0开始处理的
//       int semctl(int semid, int semnum, int cmd, ...);
//cmd,有多种.如果使用SETVAL,第四个参数是union semun联合体
//       key_t ftok(const char *pathname, int proj_id);
                                                        //nsops,struct num 
//int semop(int semid, struct sembuf *sops, unsigned nsops);
void pOptions(int id)
{
        struct  sembuf sop;
        sop.sem_num = 0;
        sop.sem_op = -1;//拿锁p操作,值减1
        sop.sem_flg=SEM_UNDO;//SEM_UNDO对锁才有所操作
        semop(id , &sop , 1);//nsops表示struct sembuf的个数,例如:struct sembuf sop[2],则nsops=2
        printf("get key\n");
}

void vOptions(int id)
{
        struct  sembuf sop;
        sop.sem_num = 0;
        sop.sem_op = 1;//拿锁v操作,值加1
        sop.sem_flg = SEM_UNDO;
        semop(id , &sop , 1);
        printf("put back the key\n");
}

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) */
};

int main(int argc, char const *argv[])
{
        key_t key;
        key=ftok(".",2);
        int semid;
        semid=semget(key,1,IPC_CREAT|0666);

        union semun initsem;
        initsem.val = 0;//=0,表示刚开始给他设定无锁状态
        semctl(semid,0,SETVAL,initsem);

        int pid=fork();
        //刚开始是无锁状态,只有等待子进程放完锁父进程才能拿到锁。相当于刚开始锁给子进程拿了
        if(pid > 0){
                //去拿锁
                pOptions(semid);
                printf("This is a father PID\n");
                //放回锁
                vOptions(semid);
                semctl(semid,0,IPC_RMID);
        }

        else if(pid == 0){
                printf("This is a child PID\n");
                vOptions(semid);
        }

        else{
                printf("Touch PID failure\n");
        }

        return 0;
}

运行结果:

CLC@Embed_Learn:~/LinuxLearn/IPC$ ./a.out
This is a child PID
put back the key
get key
This is a father PID
put back the key
CLC@Embed_Learn:~/LinuxLearn/IPC$ ./a.out
This is a child PID
get key
This is a father PID
put back the key
CLC@Embed_Learn:~/LinuxLearn/IPC$ put back the key
./a.out
This is a child PID
get key
This is a father PID
put back the key
CLC@Embed_Learn:~/LinuxLearn/IPC$ put back the key
`

注:

  • 放锁和拿锁执行的函数执行的顺序是不确定的(父子进程的执行顺序),所以会出现上面的运行效果。
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值