进程通信介绍
1.当服务器在运行时,进程之间可能存在特定的协同工作的场景,比如一个进程要将数据交付给另一个进程,让其处理,可是进程是相互独立的!如果交互数据,那么成本一定很高!
2.两个进程之间要相互通信,因为进程是相互独立的,所以得必须看到一份公共的资源,这里的资源就是:内存!并且这份资源是属于OS提供的.
3.由此可知进程间通信的本质就是:OS参与,提供一份所有通信进程都能看到的公共资源!这份公共资源可能以文件方式提供,可能以队列的方式提供,也可能提供的就是原始内存块,这也就是通信方式有很多种的原因
进程通信的分类
管道
- 匿名管道pipe
- 命名管道
System V IPC
- System V 消息队列
- System V 共享内存
- System V 信号量
POSIX IPC
- 消息队列
- 共享内存
- 信号量
- 互斥量
- 条件变量
- 读写锁
管道
1.管道是UNIX中最古老的进程间通信方式.
2.我们把一个进程链接到另一个进程的一个数据流称为一个"管道"
匿名管道
#include <unistd.h>功能 : 创建一无名管道原型int pipe(int fd[2]);// fd就是文件描述符参数fd :文件描述符数组 , 其中 fd[0] 表示读端 , fd[1] 表示写端返回值 : 成功返回 0 ,失败返回错误代码
对于匿名管道的使用我们一般都是通过fork(),来共享管道.
这样一来,我们就可以让子进程来向管道中写入数据,从而让父进程读取,反之我们也可以让父进程写入,子进程来读取
int main()
{
int pipe_fd[2]={0};
if(pipe(pipe_fd)<0)
{
perror("pipe");
return 1;
}
printf("%d, %d\n",pipe_fd[0],pipe_fd[1]);
pid_t id=fork();
if(id<0)
{
perror("fork failed");
return 2;
}
else if(id==0)
{
//write
//child
close(pipe_fd[0]);
const char* msg="hello parent,I am child";
int count =0;
while(1)
{
write(pipe_fd[1],msg,strlen(msg));
sleep(1);
if(count==5)
{
break;
}
count++;
}
close(pipe_fd[1]);
exit(0);
}
else
{
//read
//father
close(pipe_fd[1]);
char buffer[64];
while(1)
{
sleep(1);
buffer[0]=0;
ssize_t size=read(pipe_fd[0],buffer,sizeof(buffer)-1);
if(size>0)
{
buffer[size]=0;
printf("parent get messge from child# %s\n",buffer);
}
else if(size==0)
{
printf("pipe file close ,child quit!\n");
break;
}
else{
break;
}
}
}
int status=0;
waitpid(id,&status,0);
close(pipe_fd[0]);
return 0;
}
站在文件描述符的角度-----深度理解管道
管道读写的四种情况
- 读端不读或读的慢,写端要等读端
- 读端关闭,写端收到SIGPIPE信号直接终止
- 写端不写或写的慢,读端要等写端
- 写端关闭,读端读完pipe内部数据然后再读,会读到0,表明读到文件结尾
匿名管道的特点
- 管道是一个只能单向通信的通信信道
- 管道是面向字节流的
- 匿名管道只能用于具有亲缘关系的进程间通信
- 管道自带同步机制,原子性写入
- 一般而言,进程退出,管道释放,所以管道生命周期是随进程的
命名管道
为了解决匿名管道只能父子间通信,引入了命名管道,本质上可以让毫不相干的两个进程相互间通信
ps:命名管道是一种特殊的文件
创建一个命名管道
- 命名管道可以从命令行上创建
$ mkfifo filename
- 命名管道也可以从程序中创建:
int mkfifo(const char *filename,mode_t mode);
举个例子:用命名管道实现server&client之间通信
int main() { umask(0); if(mkfifo(MY_FIFO,0666)<0) { perror("mkfifo"); return 1; } //只需要文件操作即可 int fd=open(MY_FIFO,O_RDONLY); if(fd<0) { perror("open"); return 2; } //业务逻辑,可以进行对应的读写 while(1) { char buffer[64]={0}; ssize_t s=read(fd,buffer,sizeof(buffer)-1);//键盘输入的时候\n也是输入字符的一部分 if(s>0) { //sucess buffer[s]=0; if(strcmp(buffer,"show")==0) { if(fork()==0) { execl("/usr/bin/sl","sl",NULL); exit(1); } waitpid(-1,NULL,0); } else { printf("client# %s\n",buffer); } } else if(s==0) { //perr close printf("client quit ...\n"); break; } else { //error perror("read"); break; } } close(fd); return 0; }
int main()
{
int fd =open(MY_FIFO,O_WRONLY);
if(fd<0)
{
perror("open");
return 1;
}
//业务逻辑
while(1)
{
printf("请输入# ");
fflush(stdout);
char buffer[64]={0};
ssize_t s=read(0,buffer,sizeof(buffer)-1);
if(s>0)
{
buffer[s-1]=0;
printf("%s\n",buffer);
write(fd,buffer,strlen(buffer));
}
}
close(fd);
return 0;
}
匿名管道与命名管道的区别
- 匿名管道由pipe函数创建并打开
- 命名管道由mkfifo函数创建,open打开(与文件操作一样)
- FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们创建与打开方式不同,一旦这些工作完成以后,它们具有相同的语义
System V 共享内存
共享内存区是最快的 IPC 形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据.
共享内存的数据结构
struct shmid_ds {
struct ipc_perm shm_perm; /* operation perms */
int shm_segsz; /* size of segment (bytes) */
__kernel_time_t shm_atime; /* last attach time */
__kernel_time_t shm_dtime; /* last detach time */
__kernel_time_t shm_ctime; /* last change time */
__kernel_ipc_pid_t shm_cpid; /* pid of creator */
__kernel_ipc_pid_t shm_lpid; /* pid of last operator */
unsigned short shm_nattch; /* no. of current attaches */
unsigned short shm_unused; /* compatibility */
void *shm_unused2; /* ditto - used by DIPC */
void *shm_unused3; /* unused */
};
shmget函数
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat函数
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
ps:
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr -
(shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
举个例子:用共享内存实现server&client之间通信
int main()
{
key_t key=ftok(PATH_NAME,PROJ_ID);
if(key<0){
perror("ftok");
return 1;
}
int shmid=shmget(key,SIZE,IPC_CREAT|0666);
if(shmid<0){
perror("shmget");
return 2;
}
printf("key:%u ,shmid: %d\n",key,shmid);
char* mem =(char*)shmat(shmid,NULL,0);
printf("attaches shm success\n");
while(1)
{
printf("%s\n",mem);
sleep(1);
}
shmdt(mem);
printf("detaches shm success\n");
shmctl(shmid,IPC_RMID,NULL);
printf("key,0x%x,shmid: %d->shm delete success\n",key,shmid);
return 0;
}
int main()
{
key_t key=ftok(PATH_NAME,PROJ_ID);
if(key<0){
perror("ftok");
return 1;
}
printf("%u\n",key);
int shmid=shmget(key,SIZE,IPC_CREAT);
if(shmid<0){
perror("shmid");
return 1;
}
char* mem=(char*)shmat(shmid,NULL,0);
printf("client process attaches success\n");
char c='A';
while(c<='Z'){
mem[c-'A']=c;
c++;
mem[c-'A']=0;
sleep(1);
}
shmdt(mem);
printf("client process detaches success\n");
return 0;
}
共享内存总结
- 共享内存的本质就是开辟一块物理内存,让多个进程映射同一块物理内存到自己的地址空间进行访问,实现数据共享的。
- 共享内存的操作是非进程安全的,多个进程同时对共享内存读写是有可能会造成数据的交叉写入或读取,造成数据混乱
- 共享内存的删除操作并非直接删除,而是拒绝后续映射,只有在当前映射链接数为0时,表示没有进程访问了,才会真正被删除
- 共享内存生命周期随内核,只要不删除,就一直存在于内核中,除非重启系统(当然这里指的是非手动操作,可以手动删除)
- 共享内存是所有进程通信中速度最快的!