Linux进程间通信(IPC)、管道、消息队列、共享内存、信号量、信号

管道、消息队列、共享内存、信号量、信号

一、管道

定义:管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

1.1 无名管道

特点

  1. 半双工。(同一时间,数据单向流通)
  2. 只应用在父子或者兄弟进程之间。
  3. 数据读取后,管道中的数据即不存在了。
进程1管道进程2写入数据1读取数据1读取数据2写入数据2同时进程1管道进程2
//头文件
##include <unistd.h>
//主函数
int pipe(int fd[2]);    
// 返回值:success返回0,error返回-1

参数说明
当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。

1.2 命名管道(FIFO)

特点:

  1. FIFO可以在无关进程之间交换数据。
  2. FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
//头文件
##include <sys/stat.h>
//主函数
int mkfifo(const char *pathname, mode_t mode);  
// 返回值:success返回0,error返回-1

参数说明
其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。
应用区别
无名管道:pipe(fd) 和 fork() 联用;
有名管道:mkfifo(path, mode) 和 open() 联用;

二、消息队列

消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
特点

  1. 消息具有特定的格式以及特定的优先级。
  2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
  3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

前提结构体

// 消息结构
 struct msg_form {
  	 long mtype;
 	 char mtext[256];
};

最简单的消息队列使用流程

  1. ftok函数生成键值;
  2. msgget函数创建或打开消息队列;
  3. msgsnd函数添加消息
  4. msgrcv函数读取消息
  5. msgctl删除消息队列
// 头文件
#include <sys/ipc.h>
//主函数
key_t ftok(const char*path,int id);
//返回 success 返回 key值,  error返回 -1

参数说明
path 为 已经存在的路径名
id 为 0~255之间的数值
举例
key_t key =ftok("/tmp",66);

// 头文件
#include <sys/msg.h>
//主函数
 int msgget(key_t key, int flag); // 成功返回队列ID,失败返回-1
 int msgsnd(int msqid, const void *ptr, size_t size, int flag);//成功返回0,失败返回-1
 int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);//成功返回消息数据的长度,失败返回-1
 int msgctl(int msqid, int cmd, struct msqid_ds *buf);//成功返回0,失败返回-1

shell窗口指令
ls -a查看文件前的点
ls -i查看文件索引节点号
例如:文件索引节点号 65538 ,换成16进制为 0x010002,而你指定的id号为38,16进制为0x26,key_t返回值为0x26010002

三、共享内存

共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

特点

  1. 进程是直接对内存进行存取。
  2. 因为多个进程可以同时操作,所以需要进行同步。
  3. 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

常见内存交流的流程

  1. shmget函数创建或获取一个共享内存;
  2. shmat函数连接共享内存到当前进程的地址空间;
  3. shmdt函数断开共享内存;
  4. shmctl函数删除共享内存;
//头文件
#include <sys/shm.h>
//主函数
int shmget(key_t key, size_t size, int flag); // 成功返回共享内存ID,失败返回-1
void *shmat(int shmid, const void *shmaddr, int shmflag); // 成功返回指向共享内存的指针,失败返回-1
int shmdt(void *addr); // 成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);  //成功返回0,失败返回-1

参数说明
1、int shmget(key_t key, size_t size, int flag);
size必须1M以上;

// 例如:
shmget(key,1024*4,IPC_CREAT|0666);

2、void *shmat(int shmid, const void *shmaddr, int shmflag);
shmid:共享内存地址
shmaddr:取0,内核决定地址段
shmflag:取0,可读可写

3、int shmdt(void *addr);
addr:shmat 返回共享内存指针;

4、int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
cmd:一般取IPC_RMID(删去);
buf:取0;

// 例如:
shmctl(shmid,IPC_RMID,0);

shell窗口指令
ipcs -m:查看当前有哪些共享内存
ipcrm -m:删掉对应id号的共享内存

缺点:需要控制共享内存写入顺序。
解决方法:信号量来控制。

四、信号量

它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
特点

  1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
  2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
  3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
  4. 支持信号量组。

类比房间和锁的关系:
钥匙:信号量
房间:临界资源
信号量集:linux下信号量总体
p操作:拿锁
v操作:放回锁

常见信号量操作的流程
1、创建或获取一个信号量组;
2、初始化信号量;
3、对信号量组进行操作,改变信号量的值;

// 头文件
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);// 若成功返回信号量集ID,失败返回-1
int semctl(int semid, int sem_num, int cmd, ...);// 初始化信号量的相关信息
int semop(int semid, struct sembuf semoparray[], size_t numops);// 成功返回0,失败返回-1

参数说明
1、int semget(key_t key, int num_sems, int sem_flags);
key:与消息队列和共享内存一样
num_sems:信号量个数
sem_flags:IPC_CREAT|0666
2、int semctl(int semid, int sem_num, int cmd, …);
semid:semget返回值semid
sem_num:信号组中对应的序号,0~sem_nums-1
cmd:

  • SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
  • IPC_RMID:删除一个信号量集合。
  • union semun {
    int val; // SETVAL使用的值
    struct semid_ds *buf; // IPC_STAT、IPC_SET 使用缓存区
    unsigned short *array; // GETALL,、SETALL 使用的数组
    struct seminfo *__buf; // IPC_INFO(Linux特有) 使用缓存区
    };

3、int semop(int semid, struct sembuf semoparray[], size_t numops);
semoparray[]:

  • struct sembuf {
    short sem_num; // 信号量组中对应的序号,0~sem_nums-1
    short sem_op; // 信号量值在一次操作中的改变量
    short sem_flg; // IPC_NOWAIT, SEM_UNDO
    };

numops:信号操作结构体数量。numops>>=1

五、信号

实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。

5.1 信号的名字和编号

每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
信号定义在signal.h头文件中,信号名都定义为正整数。
具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。
kill -l:查看全部信号的编号(signum)
kill -9:杀死进程
kill -SIGKILL + pid号:杀死进程

5.2 信号处理函数的注册和发送

信号处理函数的注册不只一种方法,分为简单版和高级版
简单版:signal函数
高级版:sigaction函数

信号处理发送函数
信号发送函数也不止一个,同样分为简单版和高级版
简单版:kill函数
高级版:sigqueue函数

信号注册函数——简单版signal

// 头文件
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
  1. typedef void (*sighandler_t)(int);中断函数的原型中,有一个参数是 int类型,显然也是信号产生的类型,方便使用一个函数来处理多个信号。
  2. 对于sighandler_t signal(int signum,sighandler_t handler);函数来说,signum 显然是信号的编号,handler 是中断函数的指针。
// 例如:
	signal(SIGIO, handler);    //SIGIO文件描述符准备就绪, 可以开始进行输入/输出操作
    signal(SIGUSR1, handler);		//SIGUSR1留给用户使用

信号发送函数——简单版kill

// An highlighted block
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);

参数说明:
pid 就是接受者的 pid,sig 则是发送的信号的类型

关于 kill 函数,还有一点需要额外说明,上面的程序限定了 pid 必须为大于0的正整数,其实 kill 函数传入的 pid 可以是小于等于0的整数。
pid > 0:将发送个该 pid 的进程
pid == 0:将会把信号发送给与发送进程属于同一进程组的所有进程,并且发送进程具有权限想这些进程发送信号。
pid < 0:将信号发送给进程组ID 为 pid 的绝对值得,并且发送进程具有权限向其发送信号的所有进程
pid == -1:将该信号发送给发送进程的有权限向他发送信号的所有进程。(不包括系统进程集中的进程)

信号注册函数——高级版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只能任选其一

参数说明

  1. 第一个参数signum应该就是注册的信号的编号;第二个参数act如果不为空说明需要对该信号有新的配置;第三个参数oldact如果不为空,那么可以对之前的信号配置进行备份,以方便之后进行恢复。
  2. struct sigaction *oldact:保存上一次信息。
  3. (*sa_sigaction)(int, siginfo_t *, void *);
// siginfo_t 结构体
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 */
}
  1. 总结需要配置:const struct sigaction *act;(*sa_sigaction)(int, siginfo_t *, void *);sa_flags;

信号发送函数——高级版sigqueue

// 头文件
#include <signal.h>
//主函数
int sigqueue(pid_t pid, int sig, const union sigval value);
//结构体
union sigval {
   int   sival_int;
   void *sival_ptr;
 };

使用这个函数之前,必须要有几个操作需要完成

  1. 使用 sigaction 函数安装信号处理程序时,制定了 SA_SIGINFO 的标志。 sigaction 结构体中的
    sa_sigaction 成员提供了信号捕捉函数。如果实现的时 sa_handler 成员,那么将无法获取额外携带的数据。
  2. sigqueue 函数只能把信号发送给单个进程,可以使用 value 参数向信号处理程序传递整数值或者指针值。

sigqueue 函数不但可以发送额外的数据,还可以让信号进行排队(操作系统必须实现了 POSIX.1的实时扩展),对于设置了阻塞的信号,使用 sigqueue 发送多个同一信号,在解除阻塞时,接受者会接收到发送的信号队列中的信号,而不是直接收到一次。

atoi():字符型转换成整型;
int sprintf(char *str, const char *format, …):format内容写入str字符串里。

管道举例

无名管道

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
	int fd[2];
	pid_t pid;
	char readBuff[128];
		
	if(pipe(fd)<0){
		printf("pipe create failure\n");
	}	
	if((pid=fork())<0){
		printf("pipe fork failure\n");
	}
	else if(pid>0){
		sleep(3);
		printf("this is father\n");
		close(fd[0]);
		
		write(fd[1],"hello world",strlen("hello world "));
		
		wait();
	}else{
		printf("this is child\n");
		close(fd[1]);
		read(fd[0],readBuff,128);	
		printf("readBuff=%s\n",readBuff);
		exit(0);
	}

	return 0;
}

命名管道——读取

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

int main()
{
	char buf[128]={0};
	int nread =0;
	int count=0;

//      int mkfifo(const char *pathname, mode_t mode);
	if((mkfifo("./file",0600)== -1) && errno != EEXIST){
		printf("mkfifo failure\n");
		perror("why");
	}else{
		if(errno == EEXIST){
			printf("file exist\n");
		}else{
			printf("mkfifo success\n");
		}
	}
	int fd =open("./file",O_RDONLY);

		printf("open success\n");
	while(count<=12){
		nread=read(fd, buf, 30);
		printf("read %d byte from fifo, context=%s\n",nread,buf);
		count++;
	}
	close(fd);
	return 0;
}

命名管道——写入

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
	char *str="this is message from fifo";
	int nwrite =0;
	int cnt =0;
	int fd =open("./file",O_WRONLY);
	printf("open success\n");
	
	while(cnt<=10){
		nwrite=write(fd, str, strlen(str));
		sleep(1);
		cnt++;
	}
	close(fd);
	return 0;
}

消息队列举例

先收再发

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

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

int main()
{
	key_t key;
	key=ftok(".",26);
//       int msgget(key_t key, int msgflg);
	int msgId=msgget(key,IPC_CREAT|0777);
	if(msgId == -1){
		printf("quene creat failure\n");
	}
	
//       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);
	struct msgbuf readBuf;
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
	printf("read from quene:%s\n",readBuf.mtext);
	
	struct msgbuf sendBuf={988,"thank for your reach "};
	msgsnd(msgId,&sendBuf,sizeof(sendBuf.mtext),0);
	msgctl(msgId,IPC_RMID,NULL);
	return 0;
}

先发再收

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

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

int main()
{
	key_t key;
	key=ftok(".",26);
//       int msgget(key_t key, int msgflg);
	int msgId=msgget(key,IPC_CREAT|0777);
	if(msgId == -1){
		printf("quene creat failure\n");
	}
	
//       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);
	
	struct msgbuf sendBuf={888,"this is message from quene "};
	msgsnd(msgId,&sendBuf,sizeof(sendBuf.mtext),0);

	struct msgbuf readBuf;
	msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
	printf("return from rcvsnd quene:%s\n",readBuf.mtext);
	
	msgctl(msgId,IPC_RMID,NULL);
	return 0;
}

共享内存举例

#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.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("shm creat failure\n");
		exit(-1);
	}
	shmaddr=shmat(shmId,0,0);

	printf("shmadder creat success\n");
	strcpy(shmaddr,"liqingtian haoshuai");
	
	printf("please wait 10sec\n");	
	sleep(10);
	shmdt(shmaddr);
	shmctl(shmId,IPC_RMID,0);

	return 0;
}

信号量举例

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

//       int semget(key_t key, int nsems, int semflg);
//       int semctl(int semid, int semnum, int cmd, ...);

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) */
};
//  struct sembuf{
//           unsigned short sem_num;  /* semaphore number */
//           short          sem_op;   /* semaphore operation */
//           short          sem_flg;  /* operation flags */
//}


void pGetKey(int id)
{
 		struct sembuf set;
 		set.sem_num=0;//取第一个信号
 		set.sem_op=-1;//拿走
		set.sem_flg=SEM_UNDO;//等待放回再拿
		
		semop(id,&set,1);
		printf("getkey\n");
}

void vPutBackKey(int id)
{
 		struct sembuf set;
 		set.sem_num=0;//取第一个信号
 		set.sem_op=1;//拿走
		set.sem_flg=SEM_UNDO;//等待放回再拿
		
		semop(id,&set,1);
		printf("putbackkey\n");

}

int main()
{
		key_t key;
		key=ftok(".",1);
		int semId=semget(key,1,IPC_CREAT|0666);
				
		union semun initsem;
		initsem.val=0;
		semctl(semId,0,SETVAL,initsem);

		int pid =fork();
		if(pid>0){
				pGetKey(semId);
				printf("this is father\n");
				vPutBackKey(semId);
				semctl(semId,0,IPC_RMID);
		}else if(pid == 0){
			//	pGetKey(semId);
				printf("this is child\n");
				vPutBackKey(semId);
			//	semctl(semId,0,IPC_RMID);
		}else{
			printf("fork process is error\n");
		
		}

		return 0;
}

信号举例

简单版:signal函数

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

//       typedef void (*sighandler_t)(int);

//       sighandler_t signal(int signum, sighandler_t handler);
void handler(int signum)
{	
	switch (signum){
		case 2:	
			printf("signum =%d,SIGINT\n",signum);
			break;
		case 3:	
			printf("signum =%d,SIGQUIT\n",signum);
			break;
		case 10:	
			printf("signum =%d,SIGUSR1\n",signum);
			break;
	}

	printf("never quit\n");
}
int main()
{	
	signal(SIGINT,handler);
	signal(SIGQUIT,handler);
	signal(SIGUSR1,handler);
	while(1);	
	return 0;
} 

简单版:kill函数

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

//       int kill(pid_t pid, int sig);
int main(int argc, char **argv)
{
	int signum;
	int pid;
	signum=atoi(argv[1]);
	pid=atoi(argv[2]);
	
	printf("pid=%d,signum=%d\n",pid,signum);
	kill(pid, signum);
	printf("send singal ok\n");
	return 0;
}

高级版:sigaction函数

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

//       int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
/*           struct sigaction {
               void     (*sa_handler)(int);
               void     (*sa_sigaction)(int, siginfo_t *, void *);
               sigset_t   sa_mask;
               int        sa_flags;
               void     (*sa_restorer)(void);
           };
*/

void handler(int signum, siginfo_t*info, void* context)
{
	printf("signum=%d\n",signum);
	if(context!=NULL){
		printf("get context =%d\n",info->si_int);

		printf("get context =%d\n",info->si_value.sival_int);
	
		printf("get from %d\n",info->si_pid);
	}
}
int main()
{
	struct sigaction act;
	act.sa_sigaction=handler;
	act.sa_flags=SA_SIGINFO;
	printf("pid=%d\n",getpid());
	
	sigaction(SIGUSR1,&act,NULL);
	while(1);
	return 0;
}

高级版:sigqueue函数

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

//       int sigqueue(pid_t pid, int sig, const union sigval value);
/*           union sigval {
               int   sival_int;
               void *sival_ptr;
           };
*/
int main(int argc,char **argv)
{
	if(argc!=4){
		printf("[Arguments ERROR]\n");
		printf("ture Input:\n");
		printf("%s <Signal_Number><Target_Pid> <content>\n",argv[0]);
	}
	int signum;
	int pid;

	signum=atoi(argv[1]);
	pid = atoi(argv[2]);
	
	union sigval value;
	value.sival_int =atoi(argv[3]);
	printf("send data is %d\n",atoi(argv[3]));

	sigqueue(pid,signum,value);
	printf("pid=%d\n",getpid());
	return 0;
}

版权声明:本文为Jackice_Lee原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。