苏嵌实训——day16

一、进程间通信:

主要是利用内核空间,来完成两个进程或者多个进程之间的资源和信息的传递。
进程间通信方式(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;
}
posted @ 2023-01-29 20:21  周末不下雨  阅读(20)  评论(0编辑  收藏  举报