首页 > 其他分享 >苏嵌实训——day16

苏嵌实训——day16

时间:2023-02-04 11:37:32浏览次数:44  
标签:苏嵌 return int char day16 实训 printf include buf


文章目录

  • ​​一、进程间通信:​​
  • ​​1.传统通信方式:​​
  • ​​2. IPC通信方式(第五代操作系统):​​
  • ​​(1)传统通信之无名管道​​
  • ​​(2)传统通信方式之有名管道​​
  • ​​(3)使用有名管道来实现非亲缘间进程之间的通信​​
  • ​​(4)传统通信方式之信号​​
  • ​​3. 信号的相关的函数​​
  • ​​1.signal​​
  • ​​2. alarm​​
  • ​​4. IPC通信linux命令​​
  • ​​5. IPC通信之消息队列​​
  • ​​5.1消息队列的函数接口​​
  • ​​1.ftok​​
  • ​​2. msgget​​
  • ​​3. msgsnd​​
  • ​​4. msgrcv​​
  • ​​5. msgctl​​
  • ​​6. IPC通信之共享内存​​
  • ​​6.1 共享内存的接口​​
  • ​​1.shmget​​
  • ​​2. shmat​​
  • ​​3.shmdt​​
  • ​​4.shmctl​​

一、进程间通信:

主要是利用内核空间,来完成两个进程或者多个进程之间的资源和信息的传递。
进程间通信方式(7大类)

1.传统通信方式:

1.无名管道 ---- 使用的队列
2.有名管道 ---- 使用的队列
3.信号 ---------- 异步的方式

2. IPC通信方式(第五代操作系统):

1.消息队列 ---- 管道的集合
2.共享内存 ---- 地址映射的方式
3.信号灯集 ---- 信号灯的集合
3.网络通信
套接字:socket

(1)传统通信之无名管道

附加:

单工通信方式:任何时间点,只能由一方发送给另一方,方向不允许改变

半双工通信方式:同一个时间内,只允许有一方发送给另一方,具有双方通信的能力

全双工通信方式:任意时间点,双方任意可以给对方发送信息。

无名管道的介绍

无名管道是实现亲缘间进程通信的一种方式,属于半双工通信方式,类似于一个水管,只有两端,一个是数据流入段(写段),数据流出段(读段)。

这两个段都是固定的端口,遵循数据的先进先出,数据拿出来后就消失。管道是有有限长度的64*1024(64K)个字节,无名管道,不在文件系统上体现,

数据存在内存之上,进程结束后,数据就会丢失,管道文件不能使用lseek读写指针偏移。

无名管道的原理图:

苏嵌实训——day16_有名管道

函数接口
创建一个无名管道(pipe)

头文件:#include <unistd.h>

原型:int pipe(int pipefd[2]);
功能:创建一个无名管道,会将读写端两个文件描述符分别封装到fd[0]和fd[1]
参数:
fd[0] -----r
fd[1] -----w
返回值:
成功返回0;
失败返回-1;

管道注意点

1.如果管道中没有数据,read读取时会阻塞等待数据的到来

#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd[2] = {0};
if(pipe(fd) == -1)
{
perror("pipe");
return -1;
}
char buf[123] = {0};
ssize_t ret = read(fd[0],buf,sizeof(buf));
if(-1 == ret)
{
perror("read");
return -1;
}
printf("读到的数据为%s\n",buf);
return 0;
}

2.管道符和先进先出的原则,数据读走后就会消失

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


int main(int argc, char const *argv[])
{
int fd[2] = {0};
if(pipe(fd) == -1)
{
perror("pipe");
return -1;
}
write(fd[1],"hello world",11);
char buf[123] = {0};
ssize_t ret = read(fd[0],buf,5);
if(-1 == ret)
{
perror("read");
return -1;
}
printf("读到的数据为%s\n",buf);
read(fd[0],buf,6);
printf("读到的数据为%s\n",buf);
return 0;
}

3.管道的大小是64K,管道写满以后再次进行写入会阻塞等待写入,防止有效数据丢失。

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


int main(int argc, char const *argv[])
{
int fd[2] = {0};
if(pipe(fd) == -1)
{
perror("pipe");
return -1;
}
int i = 0;
char ch = 'a';
for(i = 0; i < 64*1024;i++)
{
write(fd[1],&ch,1);
}
printf("管道已经写满\n");
write(fd[1],&ch,1); //确定管道的大小,以及确定了管道写满之后再次写入会发生什么
return 0;
}

4.如果关闭了写入端口,读发生什么情况
1.管道中有数据时,将里面的数据读出来
2.管道中无数据时,管道机制会认为写端关闭,不会再有数据到来,read在做读取时阻塞
没有任何用处,read将不会阻塞等待了,便不会影响进程运行。

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


int main(int argc, char const *argv[])
{
int fd[2] = {0};
if(pipe(fd) == -1)
{
perror("pipe");
return -1;
}
char buf[123] = {0};
char buf1[123] = {0};
write(fd[1],"hello world",11);
close(fd[1]); //关闭写端
read(fd[0],buf,sizeof(buf)); //管道有数据,读取管道中的数据返回
printf("buf = %s\n",buf);
read(fd[0],buf1,sizeof(buf)); //管道无数据,不阻塞,直接返回
printf("buf = %s\n",buf1);
return 0;
}
  1. 如果读端关闭,在进行写入会发生“管道破裂”,
    是因为:如果读端关闭,写入将没有任何意义了,并且每次调用write函数写入数据都被称为有效数据。如果写入会造成有效数据的丢失,所以在写入时会出现管道破裂的问题,结束进程
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
int fd[2] = {0};
if(pipe(fd) == -1)
{
perror("pipe");
return -1;
}
close(fd[0]);
char ch = 'a';
write(fd[1],&ch,1); //关闭读通道,写入会发生管道破裂,结束进程
printf("写入成功\n");
return 0;
}

使用无名管道实现亲缘间进程通信
因为fork函数创建完子进程后,文件描述符也会被复制过去,相当于父子进程利用相同的文件描述符去操作一个文件指针,进而操作一个文件

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
//创建无名管道
int fd[2];
int fd1[2];
if(-1 == pipe(fd)) //管道1用于父进程给子进程发消息
{
perror("pipe1");
return -1;
}
if(-1 == pipe(fd1)) //管道2用于子进程给父进程发送消息
{
perror("pipe2");
return -1;
}
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
if(0 == pid)
{
//关闭无名管道文件描述符
close(fd[1]);
close(fd1[0]);
while(1)
{
char buf[123] = {0};
read(fd[0],buf,sizeof(buf));
if(strcmp(buf,"quit") == 0)
{
printf("通话结束\n");
exit(0);
}
printf("父进程说:%s\n",buf);

//开始回复消息
printf("请子进程输入:\n");
char buf1[123] = {0}; //接收要发送的数据
fgets(buf1,123,stdin); //必须要去掉\n
buf1[strlen(buf1) -1] = '\0';
write(fd1[1],buf1,strlen(buf1));
}
}
else if(pid > 0)
{
//父进程
close(fd[0]);
close(fd1[1]);
while(1)
{
printf("请父进程输入:\n");
char buf[123] ={0};
fgets(buf,123,stdin); //必须要去掉\n
buf[strlen(buf) -1] = '\0';
write(fd[1],buf,strlen(buf));
if(strcmp(buf,"quit") == 0)
{
printf("通话结束\n");
wait(NULL);
exit(0);
}
char buf1[123] = {0};
read(fd1[0],buf1,sizeof(buf1));
printf("收到子进程发过来的数据%s\n",buf1);
}
}
return 0;
}

(2)传统通信方式之有名管道

有名管道是建立在无名管道的基础上,为了完善无名管道只能用于亲缘间进程的缺点来延申出的一种进程间通信的方式,继承无名管道的所有点,有名管道在文件系统中属于一种特殊的管道文件,

虽然在文件系统上有所体现,但是它数据并不存放在磁盘上,而是存储在内存之上,进程结束,数据就丢失了。

有名管道作为一个文件系统上的文件,如果实现非亲缘间进程通信的话,需要open打开这个文件,那么两个进程分别需要以读,写权限打开。如果打开有名管道的释放,不足读写这两个权限。
open会阻塞等待另一个权限的到来。

创建有名管道

第一种方式:linux命令
mkfifo + 有名管道名字
第二种方式:c语言函数接口
头文件:#include <sys/types.h>
#include <sys/stat.h>

原型:int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道
参数:pathname:目标路径及名称
mode: 权限 例如:0666
返回值:
成功返回 0
失败返回 -1
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
//创建有名管道,不具备去检测文件存在则打开文件的功能
if(-1 == mkfifo("./myfifo",0664))
{
if(errno == EEXIST)
{
printf("文件已经存在,直接打开!\n");
}
else
{
perror("mkfifo");
return -1;
}
}
//打开有名管道
int fd = open("./myfifo",O_WRONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
printf("打开文件成功!\n");
return 0;
}

(3)使用有名管道来实现非亲缘间进程之间的通信

//read.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
//创建有名管道,不具备去检测文件存在则打开文件的功能
if(-1 == mkfifo("./myfifo",0664))
{
if(errno == EEXIST)
{
printf("文件已经存在,直接打开!\n");
}
else
{
perror("mkfifo");
return -1;
}
}
if(-1 == mkfifo("./myfifo1",0664))
{
if(errno == EEXIST)
{
printf("文件已经存在,直接打开!\n");
}
else
{
perror("mkfifo1");
return -1;
}
}
//如果进行两个进程的双方通信,还需要两个有名管道
//myfifo作为该进程的读取端 myfifo1作为写入端
//打开有名管道
int fd = open("./myfifo",O_RDONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
int fd1 = open("./myfifo1",O_WRONLY);
if(-1 == fd1)
{
perror("open1");
return -1;
}


printf("打开两个管道成功!\n");
while(1)
{
char buf[123] = {0};
read(fd,buf,sizeof(buf));
if(strcmp(buf,"quit") == 0)
{
printf("通话结束\n");
exit(0);
}
printf("buf = %s\n",buf);

//开始回复消息
printf("请输入:\n");
char buf1[123] = {0}; //接收要发送的数据
fgets(buf1,123,stdin); //必须要去掉\n
buf1[strlen(buf1) -1] = '\0';
write(fd1,buf1,strlen(buf1));
if(strcmp(buf1,"quit") == 0)
{
printf("通话结束\n");
exit(0);
}
}
return 0;
}

//write.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
//创建有名管道,不具备去检测文件存在则打开文件的功能
if(-1 == mkfifo("./myfifo",0664))
{
if(errno == EEXIST)
{
printf("文件已经存在,直接打开!\n");
}
else
{
perror("mkfifo");
return -1;
}
}
if(-1 == mkfifo("./myfifo1",0664))
{
if(errno == EEXIST)
{
printf("文件已经存在,直接打开!\n");
}
else
{
perror("mkfifo1");
return -1;
}
}
//如果进行两个进程的双方通信,还需要两个有名管道
//myfifo作为该进程的读取端 myfifo1作为写入端
//打开有名管道
int fd = open("./myfifo",O_WRONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
int fd1 = open("./myfifo1",O_RDONLY);
if(-1 == fd1)
{
perror("open1");
return -1;
}

printf("打开两个管道成功!\n");
while(1)
{
//开始发送消息
printf("请输入:\n");
char buf1[123] = {0}; //接收要发送的数据
fgets(buf1,123,stdin); //必须要去掉\n
buf1[strlen(buf1) -1] = '\0';
write(fd,buf1,strlen(buf1));
if(strcmp(buf1,"quit") == 0)
{
printf("通话结束\n");
exit(0);
}
//开始接收另一个进程发过来的消息
char buf[123] = {0};
read(fd1,buf,sizeof(buf));
if(strcmp(buf,"quit") == 0)
{
printf("通话结束\n");
exit(0);
}
printf("接收发送过来的数据为 %s\n",buf);
}
return 0;
}

(4)传统通信方式之信号

信号是什么:

信号在软件层对硬件层中断的一种模拟,是一个异步信号

中断:是一种优先级高的代码事件

苏嵌实训——day16_#include_02

linux所提供的信号:

查看所有信号:kill -l

发送信号给进程:kill + -信号码 + 进程PID

苏嵌实训——day16_进程_03

信号的原理:

在进程创建初期,会为进程创建一个信号函数表:

苏嵌实训——day16_进程_04

信号的处理方式:

1.忽略:指的是信号到来了,不采取热呢措施,如:SIGCHLD
SIGCHLD:子进程结束后给父进程发送一个信号
SIGKILL和SIGSTOP不能被忽略
2.捕捉:指的是信号到来之前,将信号函数表中信号所对应的默认函数指针修改成指向自己定义的函数----为我所用
SIGKILL和SIGSTOP不能被捕捉
3.默认:指定的是信号到来之后,去执行进程创建初期信号函数表中的默认操作

3. 信号的相关的函数

1.signal

头文件:#include <signal.h>

原型:typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能: 注册一个信号函数,一般在进程刚开始的时候
参数:
signum:信号号
handler:信号的处理方式
1.忽略:SIG_IGN
2.默认:SIG_DFL
3.捕捉:指向自定义函数的指针,函数指针,指向一个返回值:void,参数:int类型的函数指针
signal函数会将该signum信号号和函数指针相绑定
返回值:
成功:返回一个函数指针指向上一次所执行的函数,保留下一个
失败:返回 SIG_ERR
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
void myfun(int signum)
{
printf("哈哈,关不掉我吧!\n");
}
int main(int argc, char const *argv[])
{
//注册信号函数
//进行信号捕捉,将SIGINT信号的处理方式改成自己的处理方式
//去执行我自己的功能
if(signal(SIGINT,myfun) == SIG_ERR)
{
perror("signal");
return -1;
}
while(1)
{
printf("主线程在干自己的事情\n");
sleep(1);
}
return 0;
}
练习:利用signal函数去完成最佳回收僵尸进程的方式
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>


void myfun(int signum)
{
pid_t pid = wait(NULL);
printf("回收成功,进程ID为 %d\n",pid);
}
int main(int argc, char const *argv[])
{
//注册信号
if(signal(SIGCHLD,myfun) == SIG_ERR)
{
perror("signal");
return -1;
}
//创建一个进程
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
else if(0 == pid)
{
//子进程
sleep(4);
exit(0);
}
else if(pid > 0)
{
while(1)
{
printf("父进程在做自己的事情\n");
sleep(1);
}
}

return 0;
}

2. alarm

头文件:#include <unistd.h>


原型:unsigned int alarm(unsigned int seconds);
功能:给自己发送一个闹钟信号 ----》SIGALRM 默认终止进程
参数:
定时seconds秒后发送信号
unsigned int ret = alarm(5); //5秒之后发送一个信号,代码正常向下执行
返回值:
成功返回上一次alarm剩余的秒数
0代表定时器时间到
注意:
如果调用alarm后再次调用alarm函数会刷新定时器的事件,打断了上一次alarm的定时,上一次的alarm不会再发送闹钟信号,会将上一次alarm剩余的描述返回回来。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
void myfun(int signum)
{
printf("自动出牌了\n");
alarm(5);
}
int main(int argc, char const *argv[])
{
if(SIG_ERR == signal(SIGALRM,myfun))
{
perror("signal");
return -1;
}
//开始出牌
alarm(5);
while(1)
{
char ch;
printf("请出牌\n");
scanf("%c",&ch); //用它来模拟出牌
getchar();
alarm(5);
}
return 0;
}

4. IPC通信linux命令

ipc通信共有三个:消息队列,共享内存,信号灯集
linux查看所有IPC对象的命令: ipcs
linux删除ipc对象的命令:
ipcrm -s ID号 :信号灯集
ipcrm -q ID号: 消息队列
ipcrm -m ID号:共享内存

5. IPC通信之消息队列

消息队列:是实现进程间通信的一种方式,是利用内核空间完成的,并且是一种全双工的通信方式,实质就是管道在内核空间的集合。

5.1消息队列的函数接口

1.ftok

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

原型:key_t ftok(const char *pathname, int proj_id);
功能:根据pathname和proj_id这两个参数生成一个key_t类型的数据,
按照内部自己的算法,pathname和proj_id这两个参数给的一样,
生成的key_t类型的数据就是一样的。
参数:
pathname:文件路径,路径必须存在
proj_id:只用这个int类型的数据的低八位,所以说我们一把在使用时都给其字符型
返回值:
成功返回生成好的键值
失败: -1

2. msgget

头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
原型:int msgget(key_t key, int msgflg);
功能:打开或者创建一个消息队列
参数:
KEY:通过秘钥键值创建一个消息队列
返回一个msgid消息队列的ID号,同一个key值生成的ID号一样
IPC_PRIVATE:直接用于亲缘间关系
自定义KEY值:
ftok:生成一个key_t类型的变量---键值
msgflg:打开消息队列的方式
IPC_CREAT:如果消息队列不存在,则创建消息队列
如果消息队列存在,则打开消息队列
例如:IPC_CREAT | 0664
IPC_EXCL:如果文件存在则报错返回
IPC_CREAT|IPC_EXCL | 0664 //如果文件不存在则创建文件
返回值:
成功:返回一个消息对垒ID号(非负数)
失败 :-1
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdio.h>

int main(int argc, char const *argv[])
{
//创建一个key_t的键值
key_t mykey = ftok("/home/jsetc",'a');
if(-1 == mykey)
{
printf("生成键值失败\n");
return -1;
}
//打开或者创建消息队列
int msgid = 0;
msgid = msgget(mykey,IPC_CREAT | 0664);
if(-1 == msgid)
{
perror("msgget");
return -1;
}
printf("创建消息队列成功!\n");
return 0;
}

3. msgsnd

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


原型:int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列放入数据
参数: msqid:目标消息队列的ID号
msgp:要发送消息的地址,必须是一个结构体类型
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
msgsz:要发送消息正文的大小
msgflg:发送消息的方式
阻塞发送:0
非阻塞发送:IPC_NOWAIT
返回值:
成功:返回 0
失败 :-1
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <string.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
struct msgbuf mybuf;
int main(int argc, char const *argv[])
{
//创建一个key_t的键值
key_t mykey = ftok("/home/jsetc",'a');
if(-1 == mykey)
{
printf("生成键值失败\n");
return -1;
}
//打开或者创建消息队列
int msgid = 0;
msgid = msgget(mykey,IPC_CREAT | 0664);
if(-1 == msgid)
{
perror("msgget");
return -1;
}
printf("创建消息队列成功!\n");
//开始发送数据
while(1)
{
//发送数据
fgets(mybuf.mtext,1024,stdin);
mybuf.mtext[strlen(mybuf.mtext) - 1] = '\0';
if(-1 == msgsnd(msgid,&mybuf,strlen(mybuf.mtext),0))
{
perror("msgsnd");
return -1;
}
}
return 0;
}

4. msgrcv

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


原型:int ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
int msgflg);
功能:从消息队列中读取数据
参数: msqid:目标消息队列的ID号
msgp:存放消息的地址,结构体要求和发送端一致
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
msgsz:要发送消息正文的大小
msgtyp:消息类型,可以等于0,如果写0,从消息队列开头读取
msgflg:发送消息的方式
阻塞发送:0
非阻塞发送:IPC_NOWAIT
返回值:
成功:成功接收到正文的大小
失败 :-1
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <string.h>
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1024]; /* message data */
};
struct msgbuf mybuf;
int main(int argc, char const *argv[])
{
//创建一个key_t的键值
key_t mykey = ftok("/home/jsetc",'a');
if(-1 == mykey)
{
printf("生成键值失败\n");
return -1;
}
//打开或者创建消息队列
int msgid = 0;
msgid = msgget(mykey,IPC_CREAT | 0664);
if(-1 == msgid)
{
perror("msgget");
return -1;
}
printf("创建消息队列成功!\n");
//开始接收数据

ssize_t ret = msgrcv(msgid,&mybuf,sizeof(mybuf.mtext),1,0);
if(-1 == ret)
{
perror("msgrcv");
return -1;
}
printf("mybuf.text = %s\n",mybuf.mtext);
return 0;

5. msgctl

头文件:#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
原型:int msgctl(int msqid,int cmd,struct msqid_ds *buf)
功能:控制消息队列
参数:
msqid:目标消息队列
cmd:如何控制,控制的方式
IPC_STAT:获取消息队列的信息,可以获取到所有msqid_ds的信息
IPC_SET:设置消息队列信息,只能设置msgid_ds里面的msg_perm结构体
IPC_RMID:删除消息队列,第三个参数直接填写NULL
buf:
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};

The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):

struct ipc_perm {
key_t __key; /* Key supplied to msgget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};

返回值:

6. IPC通信之共享内存

共享内存是利用地址映射的方式完成进程间通信,实质在内核建立一个共享的区域,然后将这片区域的地址,映射到用户空间,用户空间就有了一个映射出来的地址,
操作用户空间,就相当于直接操作了内核空间。共享内存相对来说简单一些。还有就是共享内存是IPC通信中能够效率最高的一种也是速度最快的一种。但是共享内存数据拿走不会消失。并且写入数据时会覆盖掉之间的数据。
共享内存一般用于数据的实时采集上传,本身不具备进程同步的功能。

6.1 共享内存的接口

1.shmget

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


原型:int shmget(key_t key, size_t size, int shmflg);
功能:打开或者创建一个共享内存
参数:key:
两种方式打开或者创建共享内存
IPC_PRIVATE:用于亲缘间进程
自定义key使用ftok函数
size:创建共享内存的阿晓,如果已经存在了则无效
注意:size一般采取4K的倍数,如果不采取4K的倍数,在真实的共享内存中,
会向上近似等于4K的倍数,但是IPCS不会显示出来
shmflg:打开的方式
IPC_CREAT:如果共享内存存在,则打开,不存在则创建
例如:IPC_CREAT | 0664
IPC_EXCL:如果存在则报错返回,如果不存在配合IPC_CREAT创建
返回值:
成功返回共享内存的ID号
失败返回-1

2. shmat

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

原型:void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:地址映射,将shmid所对应的共享内存的首地址映射到用户空间shmaddr地址上
参数:
shmid:目标共享内存
shmaddr:映射到用户空间的地址
默认入口:如果填写NULL。操作系统会自动分配地址
会通过返回值提供给用户
shmflg:
操作映射完的地址的权限
0:默认操作 读写权限
SHM_RDONLY:只读权限操作内存
返回值:
成功:会返回用户空间映射的地址
失败:返回(void *)-1

3.shmdt

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


原型:int shmdt(const void *shmaddr);
功能:取消地址映射
参数:
shmaddr:映射到用户空间的首地址
返回值:
成功:返回0
失败:返回-1

4.shmctl

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



原型:int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:控制共享内存
参数:
shmid:目标共享内存ID号
cmd:如何控制,控制的方式
IPC_STAT:获取共享内存属性
IPC_SET:设置共享内存属性
IPC_RMID:删除共享内存,如果说有进程正在使用,它会记录一下,等该进程断开连接后
删除
buf:
The buf argument is a pointer to a shmid_ds structure, defined in <sys/shm.h> as follows:

struct shmid_ds {
struct ipc_perm shm_perm; /* Ownership and permissions */
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 */
...
};

The ipc_perm structure is defined as follows (the highlighted fields are settable using IPC_SET):

struct ipc_perm {
key_t __key; /* Key supplied to shmget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions + SHM_DEST and
SHM_LOCKED flags */
unsigned short __seq; /* Sequence number */
};


返回值:
IPC_STAT:成功返回ID号
其它情况:返回0
失败返回-1
//read.c
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//生成一个共享内存使用的key值
key_t mykey = ftok("/home/jsetc/jsetc/208/",'a');
if(-1 == mykey)
{
perror("生成键值失败");
return -1;
}
//创建共享内存
int shmid = shmget(mykey,4096,IPC_CREAT | 0664);
if(-1 == shmid)
{
perror("shmget");
return -1;
}
printf("创建或者打开共享内存成功\n");


//地址映射
char *buf = (char *)shmat(shmid,NULL,0);
if((char *)-1 == buf)
{
perror("shmat");
return -1;
}
//开始操作共享内存
while(1)
{
printf("buf = %s\n",buf);
sleep(1);
}
return 0;
}
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <stdio.h>
int main(int argc, char const *argv[])
{
//生成一个共享内存使用的key值
key_t mykey = ftok("/home/jsetc/jsetc/208/",'a');
if(-1 == mykey)
{
perror("生成键值失败");
return -1;
}
//创建共享内存
int shmid = shmget(mykey,4096,IPC_CREAT | 0664);
if(-1 == shmid)
{
perror("shmget");
return -1;
}
printf("创建或者打开共享内存成功\n");


//地址映射
char *buf = (char *)shmat(shmid,NULL,0);
if((char *)-1 == buf)
{
perror("shmat");
return -1;
}
//开始操作共享内存
scanf("%s",buf);


//取消地址映射,断开与共享内存的连接,避免误操作
shmdt(buf);
//删除共享内存
shmctl(shmid,IPC_RMID,NULL);
return 0;
}


标签:苏嵌,return,int,char,day16,实训,printf,include,buf
From: https://blog.51cto.com/u_15909950/6036988

相关文章

  • 【广州华锐互动】钻井平台VR实训仿真教学系统
    钻井平台VR实训是一种利用虚拟现实技术进行钻井训练的新型模拟培训系统,可以为钻井行业的从业人员提供更加真实的训练环境,提高他们的技能水平。通过钻井平台VR实训,可以让从......
  • 《RPC实战与核心原理》学习笔记Day16
    23|如何在没有接口的情况下进行RPC调用?我们什么情况下需要在没有接口时进行RPC调用?列举2个典型场景:我们搭建一个测试平台,允许各个业务方在测试凭条上通过输入接口、......
  • day16-声明式事务-02
    声明式事务-023.事务的传播机制事务的传播机制说明:当有多个事务处理并存时,如何控制?比如用户去购买两次商品(使用不同的方法),每个方法都是一个事务,那么如何控制呢?也......
  • 代码随想录算法训练营day16 | leetcode ● 104.二叉树的最大深度 559.n叉树的最大深
    基础知识二叉树的多种遍历方式,每种遍历方式各有其特点LeetCode104.二叉树的最大深度分析1.0往下遍历深度++,往上回溯深度--classSolution{intdeep=0,max=......
  • day16
    1、104.二叉树的最大深度递归法二叉树的最大深度==》根节点的高度通过后序(左右中)求得根节点高度来求得二叉树最大深度。递归三要素确定递归函数的参数和返回值......
  • Day16 -闭包和装饰器
    1.闭包介绍和基本语法1.函数产生嵌套(外函数中定义一个内函数)2.内函数使用外函数定的局部变量3.外函数返回内函数的引用(函数名)闭包的介绍我们前面已经学过了......
  • 工业钢铁VR虚拟仿真实训系统助力培养高素质技术技能人才
    工业VR虚拟仿真实训系统为钢铁自动化操作、工厂设备诊断、故障维修等专业培训提供了一个很好的平台,能够极大地提升学员们对相关专业技能进行学习和训练、创新和创造能力。......
  • day16集合
    1.Collection集合1.1数组和集合的区别【理解】相同点都是容器,可以存储多个数据不同点数组的长度是不可变的,集合的长度是可变的数组可以存基本数据类型和引......
  • 【广州华锐互动】电力虚拟仿真成品实训系统介绍
    传统的现代电气控制系统和自动线装调的教学设备,器材体积过大,调试操作复杂,还有一定的危险性,许多实验因时间和场地等因素降低了学生的学习效果。广州华锐互动研发的电力虚拟......
  • 企业实训课程:一个通用后台管理项目
    empSystem企业实训:通用后台管理项目Github地址:https://github.com/Zpss2021/empSystem写在最前本项目限于原作者水平,上传时各个方面功能都不完善,只能作为一个小案例参......