苏嵌实训——day16

news2024/11/15 9:48:59

文章目录

  • 一、进程间通信:
    • 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读写指针偏移。
无名管道的原理图:
在这里插入图片描述

函数接口
创建一个无名管道(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)传统通信方式之信号

信号是什么:
信号在软件层对硬件层中断的一种模拟,是一个异步信号
中断:是一种优先级高的代码事件
在这里插入图片描述

linux所提供的信号:
查看所有信号:kill -l
发送信号给进程:kill + -信号码 + 进程PID
在这里插入图片描述

信号的原理:
在进程创建初期,会为进程创建一个信号函数表:
在这里插入图片描述

信号的处理方式:

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/185157.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python实现清除文件夹中重复视频

目录一、二进制文件二、摘要算法(MD5)三、shutil模块四、视频清除视频全在一个文件夹里视频在不同的文件夹里一、二进制文件 二进制文件是以文本的二进制形式存储在计算机中。 用户一般不能直接读取它们&#xff0c;需要通过相应的软件才能将其显示出来。 二进制文件一般是可…

jspssm大学生宿舍管理系统-宿管带前端

目录 摘 要 II Abstract III 1 绪论 1 1.1 课题背景 1 1.2 课题研究现状 1 1.3 初步设计方法与实施方案 2 1.4 本文研究内容 2 2 系统开发环境 4 2.1 JSP技术 4 2.2 B/S架构 5 2.3 Eclipse环境配置 5 2.4 MySQL数据库 6 3 系统分析 7 3…

【微服务】Docker容器化

&#x1f6a9;本文已收录至专栏&#xff1a;微服务探索之旅 &#x1f44d;希望您能有所收获 一.引入 (1) 为什么需要Docker 微服务虽然具备各种各样的优势&#xff0c;但服务的拆分的非常多给部署带来了很大的麻烦。 分布式系统中&#xff0c;依赖的组件非常多&#xff0c;不同…

【1】Python基础语法

字面量 字面量&#xff1a;在程序中&#xff0c;被写下来的固定值&#xff0c;称之为字面量。Python中常用的6种数据类型&#xff1a; 字符串&#xff08;string&#xff09;&#xff0c;又称文本&#xff0c;是由任意数量的字符如中文、英文、各类符号、数字等组成&#xff0…

虚拟化技术学习笔记10

虚拟机镜像管理 学习目标&#xff1a; 能够了解KVM虚拟机支持的镜像格式 能够使用qemu-img实现镜像创建 能够使用qemu-img实现镜像查看 能够使用qemu-img实现镜像格式转换 能够了解后备镜像的作用 能够了解差量镜像的作用 能够基于后备镜像制作差量镜像 能够使用差量镜…

[网鼎杯 2020 朱雀组]Nmap(双解详细分析)

目录 Nmap 相关参数 信息收集 思路 方法一 方法二 nmap常见操作 Nmap 相关参数 -iL 读取文件内容&#xff0c;以文件内容作为搜索目标 -o 输出到文件 -oN 标准保存 -oX XML保存 -oG Grep保存 -oA 保存到所有格式 信息收集 可以对ip进行扫描 思路 方法一 将一句话木马…

【高并发】- 生产级系统搭建 - 3

前言 本章讲解高并发系统动静分离方案设计、热点数据处理、管控等思想。 1. 动静分离方案设计 动静分离实质&#xff0c;将静态页面与动态页面&#xff08;或者静态数据与动态数据&#xff09;解耦分离&#xff0c;用不同系统承载对应流量。这样可以提升整个服务的访问性能和可…

MySql性能优化(五)优化细节

优化细节 当使用数据库列进行查询的时候尽量不要使用表达式&#xff0c;把计算结果放到业务层而不是数据层尽量使用主键索引&#xff0c;而不是其他索引&#xff0c;因此主键索引不会触发回表查询使用前缀索引 有的时候需要索引很长的字符串&#xff0c;这会让索引变的大且慢&…

Ethercat系列(1)COE非周期性数据通信

Ethercat主站通过读写邮箱数据SM通道实现非周期性数据通信。邮箱数据定义邮箱数据单元结构邮箱数据头各字段含义如下表非周期性邮箱数据通信EtherCAT协议中非周期性数据通信称为邮箱数据通信&#xff0c;它可以双向进行---主站到从站和从站到主站。它支持全双工&#xff0c;两个…

存储介质还是存储载体,这不是个问题

在档案领域中&#xff0c;“介质”和“载体”到底有什么区别&#xff1f;能不能混用&#xff1f;这个问题曾经困扰了笔者10几年&#xff0c;直到最近才发觉原来根本不是一个问题。我们先来看两句话&#xff1a; 1、磁盘、光盘、固态硬盘等常见数据存储载体的寿命无法满足电子档…

RabbitMQ快速入门和使用

文章目录1. 基础理论1.1. 同步调用与异步调用1.2. RabbitMQ 安装与运行1.2.1. 常见消息模型2. 基本消息队列的应用2.1. 消息发送流程2.2. 消息接收流程3. SpringAMQP的基础理论与应用(想快速应用看这里)3.1. 基础理论3.2. 【案例一】实现HelloWorld中的基础消息队列功能3.3. 【…

Neo4j数据库模糊查询

1、Neo4j单个查询条件模糊查询1.1使用 ~’.模糊匹配对象.’ 进行表示1.1.1 查询节点MATCH(n:Author) WHERE n.name ~.*梦.* RETURN n1.1.2 查询关系MATCH p({title:锆石U-Pb和Lu-Hf同位素研究内蒙乌努格吐山斑岩型铜钼矿岩浆岩特征})-[r:has_illustration]->(i:Illustration…

Python应用开发——制作bin文件

Python应用开发——制作bin文件 目录Python应用开发——制作bin文件前言1 环境搭建2 代码编写与测试结束语前言 什么是bin&#xff1f; bin是二进制文件&#xff0c;其用途依系统或应用而定。一种文件格式binary的缩写。一个后缀名为".bin"的文件&#xff0c;只是表…

无代码配置态势感知分析应用,预判你的预判

森林发生火势灾情时&#xff0c;应急管理中心快速做出部署&#xff0c;实时监测并分析演变趋势大型交通事故发生&#xff0c;应急指挥中对前端人员、车辆的远程调度线路规划实时监测无人机对电站电厂的运维工作公安警务对嫌疑人、嫌疑车辆等目标的跟踪侦查……以上这些&#xf…

Acwing---基础算法(一)

文章目录 快速排序归并排序整数二分浮点数二分一、快速排序 #include<iostream>using namespace std;const int N 1e6 10;int n; int q[N];void quick_sort(int q[], int l, int r) {//此时区间只有一个数或者没有数不需要排序了if (l > r) return;int x q[l], i…

【QCA】【实例】高通DTS中添加BLSP设备节点的基本配置

文章目录0. env1. 简介2. 需求及资料2.1 需求2.2 资料3. DTS实际配置及简述0. env IPQ601x SPF11.5cs 1. 简介 BLSP(BAM Low-Speed Peripheral) 不同chip有不止一组的BLSP&#xff0c;每个BLSP又有多个端口供使用。 每个端口功能可以复用为&#xff1a;SPI 或者 I2CUART 2. 需求…

计算机网络知识详解之:TCP连接原理详解

网络知识详解之&#xff1a;TCP连接原理详解 文章目录网络知识详解之&#xff1a;TCP连接原理详解TCP连接三次握手一、准备工作二、一次握手三、二次握手四、三次握手为什么TCP客户端最后还要发送一次确认呢&#xff1f;为什么要3次握手?握手中的SYN超时重试TCP协议缺陷四次挥…

HECS 安装mqtt

下载(hecs &#xff0c;centos8的样子&#xff0c;对mosquitto 版本高了安装不了)wget https://mosquitto.org/files/source/mosquitto-1.5.9.tar.gztar -zxvf mosquitto-1.5.9.tar.gz安装前置工具yum install gcc-cyum install cmakeyum install openssl-devel编译cd mosquitt…

Vue项目启动后跳转到制定路由页面

前言 今天把自己的项目布局完成了&#xff0c;但是想在项目启动的时候默认跳转到登录页面。 这其实需要借助路由实现跳转 开始编写之前&#xff0c;大家可以看下我的布局&#xff1a; 安装并使用路由 关于如何安装并使用路由&#xff0c;可以参考&#xff1a;Vue安装并使用路…

LeetCode_单周赛_330

6337. 统计桌面上的不同数字 代码 后面出现的数字都是小于 n 的。 n 1 时&#xff0c;答案是 1。 n > 1时&#xff1a; 第一天&#xff0c;n % (n - 1) 1&#xff0c;n - 1会被加入第二天&#xff0c;(n - 1) % (n - 2) 1&#xff0c;n - 2 被加入 递推&#xff0c;一…