Linux编程—系统编程(进程通信)

推荐博文

进程通信IPC

InterProcess Communication
多个进程间的交互
方式:
1.管道(无名管道和命名管道)pipo FIFO
2.消息队列
3.共享内存
4.信号
5.信号量
6.还有Socket 、Streams用于不同主机间两个进程的通信

管道

pipe

1.无名管道:半双工,具有固定读写端。用于具有情缘关系的父子进程或兄弟进程。它可看作特殊文件,对其的读写使用系统调用的read/write,注意它不属于任何文件系统,只存在于内存中。
原型:**int pipe(int pipefd[2]);**成功返回0,失败返回-1;
原理:一个管道建立会创建两个文件描述符,f[0]为读端,f[1]为写端。
demo:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(void)
{
	int fd[2];//定义管道所用的文件描述符
	char readbuf[30];//定义缓冲区
	pid_t pid;
	
	if(pipe(fd)<0)
	{
		printf("creat pipe fail!!!\n");
		exit(-1);
	}
	pid=fork();
	if(pid<0)
	{
		printf("creat process fail!\n");
		close(fd[0]);
		close(fd[1]);
		exit(-2);
	}else if(pid>0){//父进程设置为写
		close(fd[0]);//关闭读端
		write(fd[1],"test",5);
		close(fd[1]);
	}else{          //子进程设置为读
		close(fd[1]);//关闭写端
		read(fd[0],readbuf,5);
		close(fd[0]);
	}
	printf("OK.child process read:%s\n",readbuf);//此处会执行打印两次,父进程那次为乱码,因为readbuf没有初始化。
	return 0;
}

FIFO

2.命名管道FIFO:可在无关进程之间交换数据,命名管道与路径有关,它以特殊设备文件形式存在于文件系统中。
原型:int mkfifo(const char *pathname, mode_t mode);//创建实现管道功能的特殊设备文件。
原理:先创建一个作为管道的特殊设备文件,不同进程可打开设备文件进行读写操作。
demo:

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

int main(void)
{
	int ret = mkfifo("./FIFO",0600);
	if(ret==0)
	{
		printf("creat FIFO successful!!!\n");
	}else{
		printf("have the file OR creat file fail!!!\n");
		perror("why");
	}
	//如果成功创建文件,可以用file指令看看文件的属性。(ls -l)
	return 0;
}
/*
	if(mkfifo("./FIFO",0600) == -1 && errno ==EEXIST)
	{
		printf("creat file fail!!!\n");
		perror("why");
	}else{
		if(errno == EEXIST)
		{
			printf("have the FIFO file.\n");
		}else printf("creat FIFO successful!!!\n");
	}	 
*/

基于上方代码创建的FIFO文件,进行进程通信

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

int main(void)
{
	int fd;
	char buf[50]={0};
	fd=open("./FIFO",O_RDONLY);
	if(fd<0)
	{
		printf("open FIFO fail!!!\n");
		//以读的方式打开的FIFO没有写入会阻塞
		//因为默认没有设置非阻塞标志O_NONBLOCK
	}
	printf("open FIFO successful.\n");
	read(fd,buf,sizeof(buf));
	printf("BUF:%s\n",buf);
	close(fd);
	return 0;
}
/*//再写一份写操作的代码,只有进行写打开操作,读打开才会结束阻塞状态。
	int fd;
	fd=open("./FIFO",O_WRONLY);
	if(fd<0)
	{
		printf("write open FIFO fail!!!\n");
		exit(-1);
	}else{
		write(fd,"This is test.",20);
	}
	close(fd);		
*/

消息队列

特点:
消息队列是消息的链接表,放在内核中,由一个标识符(队列ID)来标识。1.它面向记录,消息具有特定格式和特定优先级。2.它独立于发送与接收端,进程终止时消息队列及其内容不会被删除。3.消息队列可以实现消息的随机查询,不一定按先进先出的次序读取,可以按消息的类型读取。
原型:
int msgget(key_t key, int msgflg);//创建打开消息队列成功返回队列ID,失败返回-1
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);//添加消息,成功返回0,失败返回-1.
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);//读取消息,成功返回消息数据长度,失败返回-1;
int msgctl(int msqid, int cmd, struct msqid_ds *buf);//控制消息队列。成功返回0,失败返回-1
原理:
内核中创建的消息队列,数据放到队列的节点(结构体),再读取和写入后,就由内核机制管理。通过key索引值找相应的队列。通过flag(方式)打开消息队列。
demo:

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

typedef struct msgbuf
{
	long mtype;
	char mtext[128];
}MSG;

int main(void)
{
	MSG buf;
	//打开或创建消息队列,两个进程基于同一个消息队列,同一key值进行通信。
	int id=msgget(0x1234,IPC_CREAT|0777);
	if(id == -1)
	{
		printf("get msg fail!!!\n");
		exit(-1);
	}
	msgrcv(id,&buf,sizeof(buf.mtext),888,0);//队列获取
	printf("read data:%s\n",buf.mtext);
/*
	MSG sndbuf={888,"This is test program!"};
	msgsnd(id,&sndbuf,strlen(sndbuf.mtext),0);//队列发送
*/
/*
	//获取key值和消息队列移除
	key_t ftok(const char *fname,int id);//获取key值
	fname:指定文件名,一般使用当前目录
	id:子序号。范围“1-255”
	
	key_t key;
	key=ftok(".",1);
	
	msgctl(id,IPC_RMID,NULL);//移除消息队列,在发送和接收程序中都要调用
	
*/
	return 0;
}

共享内存

特点:创建一段共享内存,必须指定其size;引用已存在的共享内存,则将size指定为0;
原型:
//创建或获取一个共享内存ID,失败返回-1
int shmget(key_t key,size_t size,int flag);
//连接共享内存到当前进程的地址空间,成功返回指向共享内存的指针,失败返回-1
**void shmat(int shm_id,const void addr,int flag);
//断开与共享内存的连接,成功返回0,失败返回-1;
*int shmdt(void addr);
//控制共享内存的相关信息,成功返回0,失败返回-1;
*int shmctl(int shm_id,int cmd,struct shmid_ds buf);
原理:
创建一个共享性质的内存,两个进程中可创建指针去映射到共享内存。是基于同一块内存空间进行数据操作。以实现两个普通进程间的交互。
demo:

#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

int main(void)
{
	key_t key;
	int id;
	char *addr;
	
	key=ftok(".",1);
	//注意:共享内存的大小以M对齐
	id=shmget(key,1024*4,IPC_CREAT|0600);
	if(id==-1)
	{
		printf("creat shm fail!!!\n");
		exit(-1);	
	}
	addr=shmat(id,0,0);//共享内存地址映射
	//往共享内存中写数据
	strcpy(addr,"This is test program!\n");
	sleep(3);
	shmdt(addr);//断开映射连接
	shmctl(id,IPC_RMID,0);//销毁共享内存
	printf("quit.\n");
	/*
		//在共享内存中读数据的程序
		id=shmget(key,1024*4,0);
		if(id==-1)
		{
			printf("open shm fail!!!\n");
			exit(-1);
		}
		addr=shmat(id,0,0);
		printf("from shm data:%s\n",addr);
		shmdt(addr);
	*/
	return 0;
}

注:共享内存是全双工,所以要避免两进程同时写入数据导致混乱。可使用信号量加以约束
ipcs -m使用该指令查看创建的共享内存。
ipcrm -m 共享内存ID号删除创建的共享内存

信号

特点:
是信号,为Linux提供了一种处理异步事件的方法。比如,终端用户输入了Ctrl+c来中断程序。信号SIG开头。可用 kill -l 来查看信号的名字以及序号,不存在0号信号,kill对于信号又有特殊的应用,信号具有优先级。
信号处理的方法:忽略,捕捉,默认动作
其中的捕捉信号(信号处理函数),默认动作可使用man 7 signal来查看系统定义。杀死进程kill -9 ID
原型:
信号处理函数注册

	入门:**signal---sighandler_t signal(int signum, sighandler_t handler);**
	进阶:**sigaction---int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);**

信号发送函数

	入门:kill---
	进阶:**sigqueue---int sigqueue(pid_t pid, int sig, const union sigval value);**

原理:
信号相当于中断源,用于中断。
demo:
信号捕捉(无效Ctrl C)使用ps -aux|grep a.out 并kill -9 ID号结束

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

void handler(int signum)
{
	printf("get signal number:%d\n",signum);
	switch(signum)
	{
		case 2:
			printf("SIGINT\n");
				break;
		case 9:
			printf("SIGKILL\n");//理论上这个信号不能被捕捉。
				break;
		case 10:   
			printf("SIGUSR1\n");
				break;
		default:
			printf("Don't know.\n");
	}
}

int main(void)
{
	signal(SIGINT,handler);//信号注册
	//如果要忽略信号使用宏:SIGIGN(signal(SIGINT,SIGIGN))
	signal(SIGKILL,handler);
	signal(SIGUSR1,handler);
	while(1);
	return 0;
}

信号发送

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

//可以把该程序编译成名为kill的可执行程序,使用方法和kill 指令一样。
int main(int argc,char **argv)
{
	int signum;
	int pid;
	if(argc!=3)
	{
		printf("input error!!!\n");
		exit(-1);
	}
	signum=atoi(argv[1]);//ASCII转整型
	pid=atoi(argv[2]);

	printf("num:%d  pid:%d\n",signum,pid);
	kill(pid,signum);//向相应进程发送相应信号
	//另一种写法system();
	/*
		int i=0;
		char cmd[50];
		memset(cmd,0,sizeof(cmd));
		strcpy(cmd,"kill -");
		strcat(cmd,argv[1]);
		strcat(cmd," ");
		strcat(cmd,argv[2]);
		puts(cmd);
		system(cmd);
	*/
	//还有一种
	/*
		char cmd[50]={0};
		sprintf(cmd,"kill -%d %d",signum,pid);
		puts(cmd);
		system(cmd);
	*/
	return 0;
}

信号携带信息(进阶)

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

/*
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("get signum:%d\n",signum);
	if(context != NULL)//检测是否有数据
	{
		printf("sigInformation:%d\n",info->si_int);
		printf("sigInformation:%d\n",info->si_value.sival_int);
		printf("sigInformation:%d\n",info->si_pid);
	}
}

int main(void)
{
	struct sigaction act;
	printf("ID:%d\n",getpid());
	act.sa_sigaction = handler;
	act.sa_flags = SA_SIGINFO;
	
	sigaction(SIGINT,&act,NULL); //注册信号
	//第二个参数:收到消息后的动作
	//第三个参数:做备份
	 while(1);                                                                   
	return 0;
}

基于上段代码,发送信号

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

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

int main(int argc,char **argv)
{
	pid_t pid;
	int signum;
	union sigval si;

	if(argc!=3)
	{
		printf("input error!!!\n");
		return 0;
	}
	signum=atoi(argv[1]);
	pid=atoi(argv[2]);
	si.sival_int = 100;//发送信号携带的信息
	

	sigqueue(pid,signum,si);//信号发送
	printf("ID:%d\n",getpid());
	printf("OK\n");
	return 0;
}

信号量(semaphore)

特点:
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。信号基于操作系统的PV操作,程序对信号量操作都是原子操作。对信号量的PV操作中增减值可以是任意非负整数。并且有信号量组。
原型:
//创建或获取信号量组,成功返回信号量集ID,反之返回-1
int semget(key_t key, int nsems, int semflg);
//对信号量组进行操作,改变信号量的值:成功返回0,反之返回-1
int semop(int semid, struct sembuf *sops, size_t nsops);
//控制信号量的相关信息
int semctl(int semid, int semnum, int cmd, …);
原理:
信号量与其他IPC结构不同,用于实现进程的同步和互斥,与存储进程间通信数据无关,相当于一个计数器。
对信号量的操作:P是拿锁 V是放回锁
信号量对于管理临界资源很有用。
demo:

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

union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
	struct seminfo *__buf;
} ;

void pkey(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 vkey(int id)
{
	struct sembuf set;
	set.sem_num = 0;
	set.sem_op = 1;
	set.sem_flg=SEM_UNDO;
	semop(id,&set,1);
	printf("back key\n");
}  
     
int main(int argc,char const *argv[])
{
	key_t key;
	int semid;
	int pid;
	union semun initsem;
	initsem.val = 0;//信号量,最开始没锁(锁的初始状态)
	
	key=ftok(".",2);//获取key
	//获取/创建一个信号量集,只有一个信号量
	if((semid=semget(key,1,IPC_CREAT|0666))==-1)
	{
		printf("creat semaphore fail!!!\n");
		return -1;
	}
	//操作第0个信号量,如果有多个信号量,相当于数组的第一个。SETVAL用来设置信号量的值
	//设置为initsem共同体
	semctl(semid,0,SETVAL,initsem);//信号量初始化

	pid=fork();
	if(pid>0)
	{
		//父进程拿锁	
		pkey(semid);
		printf("father process:%d\n",getpid());
	}else if(pid==0){
		//子进程执行完放锁,父进程在之前没拿到锁,阻塞
		printf("child process:%d\n",getpid());
		vkey(semid);
	}else{
		printf("creat process fail!!!\n");
	}
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值