进程间如何进行通信[IPC](二)共享内存
talk命令
talk使用socket进行通信,从键盘和socket接收数据,键盘的输入被复制到上半窗口,通过socket发出,同时通过socket读入的字符显示到下半窗口
select系统调用
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
//成功返回满足要求的文件描述符数目,超时返回0,失败返回-1
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
select允许程序挂起,并等待不止一个文件描述符的输入
原理:
- 获得所需的文件描述符列表
- 将列表传给select函数
- select挂起直到任何一个文件描述符有数据到达
- select设置一个变量中的若干位,用于通知哪一个文件描述符有输入的数据(监听文件描述符)
//等待两个设备上的数据到达
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/time.h>
#include<string.h>
#include<stdlib.h>
//等待两个设备上的数据到达
//定义宏,用于错误处理
#define opps(m,x){ perror(m);exit(x); }
#define BUF_SIZE 1024
void showdata(char *fname,int fd)
{
char buf[BUF_SIZE];
int n;
printf("%s:",fname);
fflush(stdout);
n = read(fd,buf,BUF_SIZE);
if(n==-1)
{
opps(fname,5);
}
write(1,buf,n);
write(1,"\n",1);//向标准输出写出
}
int main(int argc,char*argv[])
{
int fd1,fd2;//关注两个文件描述符,分别对应两个设备
struct timeval timeout;//超过此事件则select返回
fd_set readfds;//关注的文件描述符输入列表
int maxfd;//max fd +1
int retval;//返回值
if(argc!=4)
{
fprintf(stderr,"usage:%s file file timeout",*argv);
exit(1);
}
//打开文件
if((fd1=open(argv[1],O_RDONLY))==-1){
opps(argv[1],2);
}
if((fd2=open(argv[2],O_RDONLY))==-1){
opps(argv[2],3);
}
maxfd = 1+(fd1>fd2?fd1:fd2);
while(1)
{
//select调用的标准流程
FD_ZERO(&readfds);//置所有位为空
FD_SET(fd1,&readfds);//关注fd1(置位fd1)
FD_SET(fd2,&readfds);//关注fd2(置位fd2)
//超时时间设置
timeout.tv_sec = atoi(argv[3]);
timeout.tv_usec = 0;
//等待输入到来
retval = select(maxfd,&readfds,NULL,NULL,&timeout);
if(retval==-1)
{
opps("select",4);
}
if(retval>0)
{
//检查哪个文件描述符来了输入
if(FD_ISSET(fd1,&readfds))
showdata(argv[1],fd1);
if(FD_ISSET(fd2,&readfds))
showdata(argv[2],fd2);
}
else{
printf("no input after %d seconds\n",atoi(argv[3]));
}
}
}
Ubuntu的鼠标在/dev/input/mouse0,mouse1
进程通信方式
高级通信方式:
文件 (通过磁盘)
管道 (通过内核)
使用命名管道可以连接不想关的进程,且可以独立于进程存在,类似FIFO先进先出队列
共享内存 (通过用户空间)
允许两个或多个进程共享物理内存的同一块区域(段)
一个共享内存段可以认做(称为)是一个进程用户空间的一部分,内核介入较少
效率最高的IPC,因为不涉及进程之间的任何数据传输(管道等通信方式需要将数据从用户空间的缓冲区复制到内核内存,且接收的进程需要将数据从内核内存再复制到其用户空间缓冲区)
共享内存一般要和其他通信方式一起使用,因为需要使用辅助手段来同步进程对共享内存的访问,以防止产生竟态条件
字节流通过FIFO传输:write将数据从内存复制到内核缓存,read将数据从内核缓存复制到内存
如果进程运行在用户空间的不同部分,同一系统中的两个进程通过使用共享的内存段来交换数据
设置访问权限后,每个进程都可以访问此段
每个进程都有指向次内存段的指针
// 创建一段共享内存/获取一段已经存在的共享内存:
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg);
//key全局唯一的标识了一段共享内存,一般是用16进制表示,非0值
//size为共享内存段大小,单位为字节,如果是获取已经存在的内存,则可以指定为0
//成功返回共享内存的标识符,失败返回-1并设置errno
shmflg:
IPC_CREAT 创建
IPC_EXCL 判断共享内存是否存在,需要和上一个一起使用 IPC_EXCL | IPC_CREAT
SHM_HUGETLB 类似mmap的MAP_HUGETLB,系统将使用大页面来为共享内存分配空间
SHM_HUGE_2MB,SHM_HUGE_1GB
SHM_NORESERVE 类似MAP_NORESERVE ,不为共享内存保留交换分区,物理内存不足时,对该共享内存执行写操作将触发SIGSEGC信号
如果成功创建,则这段共享内存所有的字节都会被初始化为0
与之关联的内核shmid_ds
结构体会被初始化(初始化具体看Linux高性能服务器编程第13章:13.6.1)
共享内存创建之后不能立刻访问,需要将其关联到进程的地址空间中,使用后也需要将它从进程地址空间中分离
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
//返回addr,指向进程的虚拟地址空间中该共享内存起点
int shmdt(const void *shmaddr);
//用于分离共享内存段
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//删除共享内存段,只有当前所有附加内存段的进程都与此进程段分离,内存段才会销毁,只有一个进程需要执行这个步骤
联到进程的地址空间中,使用后也需要将它从进程地址空间中分离
#include <sys/types.h>
#include <sys/shm.h>
void *shmat(int shmid, const void *shmaddr, int shmflg);
//返回addr,指向进程的虚拟地址空间中该共享内存起点
int shmdt(const void *shmaddr);
//用于分离共享内存段,解除当前进程和共享内存之间的关联
shmaddr为共享内存的首地址,成功返回0,失败-1
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
//删除共享内存段,只有当前所有附加内存段的进程都与此进程段分离,内存段才会销毁,只有一个进程需要执行这个步骤
参数的详细内容:
使用共享内存段
例1:
//使用共享内存段的时间和日期服务器,客户端交互
#include<stdio.h>
#include<sys/shm.h>
#include<time.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
//定义共享内存段的名字key
#define TIME_MEM_KEY 99
#define SEG_SIZE ((size_t)100)//内存段大小
#define oops(m,x){ perror(m);exit(1);}
int main()
{
int seg_id;
char *mem_ptr,*t;
long now;
int n;
seg_id = shmget(TIME_MEM_KEY,SEG_SIZE,IPC_CREAT|0777);
if(seg_id==-1)
{
oops("shmget",1);
}
mem_ptr = shmat(seg_id,NULL,0);
if(mem_ptr==(void*)-1)
{
oops("shmat",2);
}
for(n = 0;n<60;n++)
{
time(&now);//获取时间
strcpy(mem_ptr,ctime(&now));//每次将mem_ptr拷贝为ctime(&now),并非拼接,而是类似删除再写
sleep(1);
}
//删除共享内存段
shmctl(seg_id,IPC_RMID,NULL);
return 0;
}
#include<stdio.h>
#include<sys/shm.h>
#include<time.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
//定义共享内存段的名字key
#define TIME_MEM_KEY 99
#define SEG_SIZE ((size_t)100)//内存段大小
#define oops(m,x){ perror(m);exit(1);}
int main()
{
int seg_id;
char *mem_ptr,*t;
long now;
int n;
seg_id = shmget(TIME_MEM_KEY,SEG_SIZE,0777);
if(seg_id==-1)
{
oops("shmget",1);
}
mem_ptr = shmat(seg_id,NULL,0);
if(mem_ptr==(void*)-1)
{
oops("shmat",2);
}
printf("the time,direct from memory:..%s",mem_ptr);
// 与shmat函数相反,是用来断开与共享内存附加点的地址,禁止本进程访问此片共享内存
shmdt(mem_ptr);
return 0;
}

例2:
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<assert.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
//创建一段大小4k的共享内存
int shmid = shmget(100,4096,IPC_CREAT|0644);
//和当前进程进行关联
void *ptr = shmat(shmid,NULL,0);
char* str = "hello,world";
//向共享内存中写数据
memcpy(ptr,str,strlen(str));
getchar();
shmdt(ptr);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<assert.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main()
{
//获取内存
int shmid = shmget(100,0,IPC_CREAT);
//和当前进程进行关联
void *ptr = shmat(shmid,NULL,0);
printf("%s\n",(char*)ptr);
shmdt(ptr);
shmctl(shmid,IPC_RMID,NULL);
return 0;
}
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
根据指定的路径,int值(但是只会用到其中的一个字节(8位)),生成一个共享内存的key值
第二个参数的范围:0-255,一般指定一个字符:例如:'a'
操作系统如何得知一段共享内存关联了几个进程?
使用此结构体:
struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and p
ermissions */
size_t shm_segsz; /* Size of segment
(bytes) */
time_t shm_atime; /* Last attach time */
time_t shm_dtime; /* Last detach time */
time_t shm_ctime; /* Last change time */
pid_t shm_cpid; /* PID of creator */
pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */
shmatt_t shm_nattch; /* No. of current attaches */ //记录了此共享内存关联发进程个数
...
};
shmctl是标记删除共享内存,而不是直接删除
关联的进程数 = 0时才会真正删除
本文作者:ziggystardust
本文链接:https://www.cnblogs.com/ziggystardust-pop/p/16147631.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步