进程间通信知识点整理

传统的进程间通信

1.有名管道pipe
2.无名管道fifo
3.信号signal

1.无名管道pipe

查看命令: man 2 pipe

头文件:#include <unistd.h>

函数原型: int pipe(int pipefd[2]);

pipefd[2] :无名管道的两个文件描述符,int型的数组,大小为2,pipefd[0]为读端,pipefd[1]为写端

返回值:
	成功:0
	失败:-1

无名管道的特点:
a、没有名字,因此无法使用open()打开。
b、只能用于亲缘进程间(如父子进程、兄弟进程、祖孙进程等)通信。
c、半双工工作方式,读写端是分开的,pipefd[0]为读端、pipefd[1]为写端。
d、是一种特殊的文件,只存在内存中,由内核进行管理。
e、对于它的读写可以使用文件IO如read、write函数。
f、无名管道的操作属于一次性操作,如果对无名管道进行读操作,数据会被全部读走。

三种通信方式:

单工:固定一种方向进行通信  类似于:广播

半双工:同一时间只能由一端发送到另一端  类似于:对讲机

全双工:通信方向都可以,同时可以发送也可以接收  类似于:电话

特点:既然说是管道,所以可以想象成一条水管,连接两个进程,一个进程负责输入数据,另一个进程负责接收数据,反过来也一样。

所以在无名管道中也一样,无名管道的两端,每一端都可以读和写。

有下面两种情况:
情况一:一个进程,那就是一端读取一端写入。

int main(int argc, const char *argv[])
{
	int fd[2];
	int ret = pipe(fd);
	if(ret == -1)
	{
		perror("pipe");
		return -1;
	}
	//从写端fd[1]写入数据
	write(fd[1], "hello world", 11);
	char buf[100] = {0};
	//从读端fd[1]读取数据
	read(fd[0], buf, sizeof(buf));
	printf("buf=%s\n", buf);
	return 0;
}

情况二:两个亲缘进程间,若一端为读就要关闭他的写功能,另一端就只能为写关闭读功能。

int main(int argc, const char *argv[])
{
	int fd[2];
	int ret = pipe(fd);
	if(ret == -1)
	{
		perror("pipe");
		return -1;
	}
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork");
		return -1;
	}
	else if(pid == 0) //子进程
	{
		//读.先关闭写端
		close(fd[1]);
		char buf[100] = {0};
		while(1)
		{
			read(fd[0], buf, sizeof(buf));
			printf("buf=%s\n", buf);
			memset(buf, 0, sizeof(buf));
		}
	}
	else{ //父进程
		//写.先关闭读端
		close(fd[0]);
		char buf[100] = {0};
		while(1)
		{
			gets(buf);
			write(fd[1], buf, strlen(buf));
		}
	}
	return 0;
}

注意事项:

  1. 当管道中无数据时,执行读操作,读操作阻塞。
  2. 无名管道的大小是固定的,管道一旦满,写操作就会导致进程阻塞。
  3. 对无名管道的操作,类似一个队列,后写入的数据不会覆盖之前的数据,会在其后面存储,读取完的数据会从管道里面移除。
  4. 将读端关闭,向无名管道中写数据,管道破裂,进程收到信号(SIGPIPE),默认这个信号会将进程杀死 。
  5. 当管道中有数据,将写端关闭,读操作可以执行,之后数据读完,可以继续读取(非阻塞),直接返回0。

2.有名管道fifo

有名管道也叫命名管道,是在文件系统目录中存在一个管道文件,是一个文件。

管道文件仅仅是文件系统中的标示,并不在磁盘上占据空间。在使用时,在内存上开辟空间,作为两个进程数据交互的通道。

查看命令:man 3 mkfifo

头文件:
#include <sys/types.h>
#include <sys/stat.h>

3、函数原型:
int mkfifo(const char *pathname, mode_t mode);
功能: 创建有名管道
参数:
  pathname: 文件的路径名
  mode: 权限
  
在shell中使用mkfifo命令: mkfifo filename

eg:
mkfifo f1
or
int ret = mkfifo("f1",0666)if(-1 == ret)
{  
		perror("mkfifo ");
		return -1;
}

特点:

1. 有名管道存在文件系统中,数据存在内存中。
2. 可以用于无亲缘关系的进程。
3. 只有读端和写端同时存在管道才能打开成功。 

数据传输特点:

1、读端不存在时,写端写入数据将会阻塞。
2、读端意外结束,写端再写数据将会管道破裂,该进程结束。
3、有名管道的数据存储在内存中,数据交互在内核中。

有名管道和无名管道的异同点

1、相同点
open打开管道文件以后,在内存中开辟了一块空间,管道的内容在内存中存放,有两个指针—-头指针(指向写的
位置)和尾指针(指向读的位置)指向它。读写数据都是在给内存的操作,并且都是半双工通讯。
2、不同点
有名管道在任意进程之间使用,无名管道在亲缘进程之间使用。
int main(int argc, const char *argv[])
{
	pid_t pid; 
	pid = fork();//创建进程
	if(pid < 0)
	{
		perror("fork");
		return -1;
	}
	else if(pid == 0)
	{
		int fd = open("./f1", O_WRONLY);//打开创建好的管道文件
		char buf[100] = {0};
		while(1)
		{
			gets(buf);
			write(fd, buf, strlen(buf));//往管道中写入数据
		}
		close(fd);
	}
	else{
		int fd = open("./f1", O_RDONLY);//另一个进程也打开管道文件,用于通信
		char buf[100] = {0};
		while(1)
		{
			read(fd, buf, sizeof(buf));//读取数据
			printf("buf=%s\n", buf);
			memset(buf, 0, sizeof(buf));
		}	
		close(fd);	
	}
	return 0;
}

3.信号

概念:信号是在软件层次上对中断机制的一种模拟。

kill函数把信号发送给进程或进程组。

raise函数把信号发送给(进程)自身。

头文件:#include <signal.h>
函数:
int kill(pid_t pid, int  signo);
int raise(int  signo);

成功则返回0, 出错则返回-1


raise(signo);   等价于  kill(getpid(), signo);

alarm();//设置闹钟

头文件:#include <unistd.h>
函数:
unsigned int alarm(unsigned int seconds);

可以为当前进程定义闹钟,时间到了会发出SIGALRM信号。
每个进程只能有一个alarm,当重新定义alarm时,会重新计时。
如果之前定义了一个闹钟,则这次定义返回的是上次闹钟剩余的时间,否则返回0。
例如:

int main(int argc, const char *argv[])
{
	alarm(5);//定义第一次闹钟
	sleep(2);//延时2秒,实际闹钟还剩3秒
	int m = alarm(5);//定义第二次闹钟
	printf("m = %d\n", m);//返回值为第一闹钟剩余的时间3秒
	while(1);
	return 0;
}

该程序7秒后结束,第一个闹钟延时2秒后,遇到第二个闹钟,则刷新,所以2+5=7秒

pause();//程序暂停

pause函数的作用,是让当前进程暂停运行,交出CPU给其他进程去执行。
当前进程进入pause状态后,当前进程会表现为“卡住、阻塞住”。
要退出pause状态,当前进程需要被信号唤醒。

信号的三种处理方式:

1.忽略 2.默认 3.自定义信号处理函数

#include <signal.h>

typedef void (*sighandler_t)(int);
    
sighandler_t signal(int signum, sighandler_t handler);
//捕获信号,设置信号的处理方式
//handler: SIG_IGN:忽略
//         SIG_DFL:默认
//         自定义的信号处理函数

示例1:

signal(SIGINT,SIG_IGN);//表示遇到ctrl+c结束信号就忽略,继续进行程序。
SIGINT : ctrl + c 
SIGQUIT : ctrl +\
SIGTSTP : ctrl + z

示例2:

void handler(int sig)
{
	if(sig == SIGQUIT)
	{
		printf("笑脸\n");
	}
	else if(sig == SIGTSTP)
	{
		printf("哭脸\n");
	}
}
int main(int argc, const char *argv[])
{
	signal(SIGQUIT, handler);
	signal(SIGTSTP, handler);
	while(1)
	{
		printf("-----------\n");//没有遇到指定信号时,会一直打印--------
		sleep(1);
	}
	return 0;
}

system V的IPC对象

ipcs命令:

ipcs -m :查询显示当前系统的共享内存
ipcs -q :查询显示当前系统的消息队列
ipcs -s :查询显示当前系统的信号灯集

ipcrm -m shmid:删除某个共享内存
ipcrm -q msgid:删除某个消息队列
ipcrm -s semid:删除某个信号灯集

IPC步骤:

	  key						    id
ftok ----> shm_get/msg_get/sem_get ----> shmmat/shmdt/shmctrl
										 msgctrl/msgsend/msgrecv
										 semctrl/semop

ftok函数

头文件:
#include <sys/types.h>
#include <sys/ipc.h>

定义:key_t key;

函数原型:key_t ftok(const char *pathname, int proj_id); 
功能:得到key值
参数:
  pathname: 路径名
  proj_id:  1-255
    
返回值:
  成功: key
  失败: -1

4.共享内存

是一种通信效率最高的进程间通信方式,

进程间通信时直接访问内存,不需要进行数据的拷贝。

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>

步骤:

 	1. ftok
 	2. shmget
 	3. shmat
 	4. 进程间通信 fork.
 	5. shmdt
 	6. shmctl
  

int shmget(key_t key, size_t size, int shmflg);
功能:创建共享内存
参数:
  key:   key值  or   IPC_PRIVATE
  size:  共享内存的大小 1024bytes  4096bytes
  shmflg: 打开方式 IPC_CREAT|0666
			
返回值:  
  成功:shmid
  失败:-1
        
      
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能: 创建映射区域  
参数: 
  shmid:  shmid值
  shmaddr: NULL (默认:自动映射)
  shmflg:
		SHM_RDONLY: 只读
		0 : 读写 (重要!)
            
返回值:
  成功:共享内存的地址
  失败:(void *)-1
          
int shmdt(const void *shmaddr);          
功能:解除映射
参数:
  shmaddr: 共享内存的地址 shmat的返回值
返回值:
   成功:0
   失败:-1
     
int shmctl(int shmid, int cmd, struct shmid_ds *buf);    
功能: 控制函数   
参数:    
    shmid: shmid值
    cmd: IPC_RMID (删除共享内存)
    buf: NULL 
返回值:
   成功:0
   失败:-1   

示例:

int main(int argc, const char *argv[])
{
	//1.ftok得到key值
	key_t key = ftok("/", 1);
	if(key == -1)
	{
		perror("ftok");
		return -1;
	}
	//2.创建共享内存得到shmid值
	int shmid = shmget(key, 1024, IPC_CREAT|0666);
	if(shmid == -1)
	{
		perror("shmget");
		goto xxx;
	}
	//3.映射, p就是共享内存的地址
    char *p = shmat(shmid, NULL, 0);
	if(p == NULL)
	{
		perror("shmat");
		goto xxx;
	}
	//4.进程间通信(亲缘)
	pid_t pid = fork();
	if(pid < 0)
	{
		perror("fork");
		goto xxx;
	}
	else if(pid == 0) //子进程
	{
		//对共享内存写
		char buf[100] = {0};
		while(1)
		{
			gets(buf);		
			strcpy(p, buf);	
			if(strncmp(buf, "quit", 4) == 0)
			{
				break;
			}
			memset(buf, 0, sizeof(buf));
		}
	}
	else{ //父进程	
		//对共享内存读	
		while(1)
		{
			waitpid(-1, NULL, WNOHANG); 
			printf("p=%s\n", p);
			if(strncmp(p, "quit", 4) == 0)
			{
				break;
			}
			sleep(1);
		}
	}
	//5.解除映射
	int ret = shmdt(p);
	if(ret == -1)
	{
		perror("shmdt");
		goto xxx;
	}
	//6.删除共享内存
	ret = shmctl(shmid, IPC_RMID, NULL);
	if(ret == -1)
	{
		perror("shmctl");
		goto xxx;
	}
xxx:
	sleep(2);
	char buf[64] = {0};
	sprintf(buf, "ipcrm -m %d", shmid);
	system(buf);
	system("ipcs -m");
	return 0;
}

5.消息队列

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

步骤:
	1. ftok
	2. msgget
	3. 进程间通信
	4. msgsnd
	5. msgrcv
	6. msgctl
  
int msgget(key_t key, int msgflg);
功能:创建消息队列
参数:
  key: key值
  msgflg:  IPC_CREAT|0666
返回值:  
  成功:msgid值
  失败:-1

    
struct msgbuf {
  long mtype;       /* message type, must be > 0 */ 类型
  char mtext[N];    /* message data */ 正文
};

struct msgbuf msg;


int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg);    
功能:发送数据  
参数:
  msgid: msgid值
  msgp:  &msg (结构体变量的地址)
  msgsz: 消息正文的长度 sizeof(msg) - sizeof(long)
  msgflg:
		IPC_NOWAIT: 非阻塞
        0 : 阻塞
返回值:
    成功: 0
    失败:-1
 
 
    
ssize_t msgrcv(int msgid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
功能:接收数据       
参数:
  msgid:  msgid值
  msgp: &msg (结构体变量的地址)
  msgsz: 消息正文的长度 sizeof(msg) - sizeof(long)
  msgtyp: 消息的类型
  msgflg:
		IPC_NOWAIT: 非阻塞
        0 : 阻塞				 
返回值:        
     成功: 0
     失败: -1  

        
        
int msgctl(int msgid, int cmd, struct msqid_ds *buf);        
功能: 控制函数      
参数:
  msgid: msgid值
  cmd: IPC_RMID (删除消息队列)
  buf: NULL
返回值:        
   成功; 0
   失败:-1 
    

6.信号灯集

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

步骤:
	1. ftok
	2. shmget
	3. shmat

*	4. semget
*	5. semctl
  	6. 进程间通信
*	7. semop
*	8. semctl
  	
    9. shmdt
  	10. shmctl
  

int semget(key_t key, int nsems, int semflg);
功能:创建信号灯集  
参数:  
  	key:ftok的返回值 or IPC_PRIVATE
  	nsems: 设置的信号灯的数目
	semflg: 
  	IPC_CREAT|0666
返回值:
     成功:semid
     失败:-1
  
      
union semun {
      int val;  /* Value for SETVAL */  初值
      struct semid_ds *buf;  
      unsigned short  *array;
      struct seminfo  *__buf; 
};
      
int semctl(int semid, int semnum, int cmd, ...);   
功能:信号灯集控制函数   
参数:
   semid: semget的返回值
   semnum: 要设置的信号灯的编号  
   cmd:    
      IPC_RMID: 删除信号灯集
      SETVAL: 设置信号灯
   ...: 共用体的变量 or 不写     
返回值:
     成功:0
     失败:-1
 
     
     编号				初值
     0 	    sem0  	  0     
     1 	    sem1  	  1   

     子进程:写入
       sem1:-1  
       //write       
       sem0:+1  
	 父进程:读取 
       sem0:-1
       //read  
       sem1:+1  
       
           
int semop(int semid, struct sembuf *sops, unsigned nsops);     
功能:对信号灯进行PV操作  (+1 -1)    
参数:
  	semid: semget的返回值
	sops: 结构体的变量的地址
    struct sembuf{
    		unsigned short sem_num;  /* semaphore number 信号灯的编号  */
			short sem_op;   /* semaphore operation 信号灯操作-1 +1*/
			short sem_flg;  /* operation flags 0:阻塞 */
    }
	nsops: 信号灯的个数 默认就是1
返回值:
    成功:0
    失败:-1
    

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值