IO进程(进程间通信IPC)

进程间通讯 IPC

InterProcess Communication

1.进程间通信方式

1.早期进程通信

无名管道(pipe)有名管道(fifo)、信号(signal)

2.system V IPC

共享内存(shared memory)消息队列(message queue)信号灯集(semaphore set)

3.BSD

套接字(socket)

2.无名管道

2.1 特点

  1. 只能用于具有亲缘关系的进程之间的通信
  2. 半双工的通信模式,具有固定的读端fd[0]和写端fd[1]。
  3. 管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO如read、write函数。
  4. 管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符 fd[0]和fd[1]。其中fd[0]固定用于读管道,而fd[1]固定用于写管道。

2.2 函数接口

int pipe(int fd[2])

功能:创建无名管道

参数:文件描述符 fd[0]:读端  fd[1]:写端

返回值:成功 0

       失败 -1

头文件:

                #include <unistd.h>

创建无名管道进行读写:

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

//无名管道
int main(int argc, char const *argv[])
{
    char buf[65536] = "";

    int fd[2] = {0}; //fd[0]表示读端,fd[1]表示写端
    if (pipe(fd) < 0)
    {
        perror("pipe err");
        return -1;
    }
    
    printf("%d %d\n", fd[0], fd[1]);

    //读写操作
    write(fd[1], "hello", 5);
    read(fd[0], buf, 32);
    

    //结构类似队列,先进先出
    //1.当管道中没数据时,读阻塞
    //read(fd[1], "hello", 5);
    //printf("%s\n", buf);

    //但是关闭写端就不一样
    //如果管道中有数据,关闭写端可以读出数据
    //无数据,关闭写端读操作会立即返回
    //write(fd[1], "hello", 32);
    //close(fd[1]);
    //read(fd[0], buf, 32);
    //printtf("%s\n", buf);

    //2.当管道中写满数据时,写阻塞,管道空间大小为64K
    //write(fd[1], buf, 65536);
    //printf("full\n");
    //write(fd[1], "1", 1);
    //printf("after\n");

    //写满一次后,当管道中至少有4K空间时(也就是再读4K)才可以继续写,否则会阻塞
    //read(fd[0], buf, 4096);
    //write(fd[1], "6", 1);
    //printf("after\n");

    //当读端关闭,向管道中写入数据无意义,会造成管道破裂,
    //进程会收到内核的SIGPIPE
    //close(fd[0]);
    //write(fd[1], "hello", 5);

    return 0;
}

gdb调试可以管道破裂信号:

2.3 注意事项

  1. 管道中无数据操作阻塞

管道中数据关闭写端可以数据读出

管道中无数据关闭写端操作立即返回

  1. 管道中写满管道大小64K)数据操作阻塞一旦4K空间继续
  2. 只有管道读端管道中数据才有意义否则导致管道破裂管道中写入数据进程会收到来自内核SIGPIPE信号通常时Broken pipe错误)

练习:父子进程实现通信,父进程循环从终端输入数据,子进程循环打印数据,当输入quit结束。

提示:不需要加同步机制, 因为pipe无数据时读会阻塞。

考虑:创建管道是在fork之前还是之后? pipefork

思路

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    pid_t pid;
    char buf[32] = "";
    int fd[2] = {0};
    if (pipe(fd) < 0)
    {
        perror("pipe err");
        return -1;
    }

    if ((pid = fork()) < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0)
    {
        //循环打印
        while (1)
        {
            //读管道内容进buf
            read(fd[0], buf, 32);
            //判断quit结束
            if (strcmp(buf, "quit") == 0)
                break;
            //打印buf到终端
            printf("%s\n", buf);
        }
    }
    else
    {
        //循环输入
        while (1)
        {
            //scanf输入进buf
            scanf("%s", buf);
            //把buf内容写入管道
            write(fd[1], buf, strlen(buf)+1); //+1是为了写入最后的\0
            //判断quit结束
            if (strcmp(buf, "quit") == 0)
                break;
        }
    }
    wait(NULL);
    return 0;
}

3.有名管道

3.1 特点

  1. 有名管道可以使互不相关的两个进程互相通信。
  2. 有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中。但是读写数据不会存在文件中,而是在管道中。
  3. 进程通过文件IO来操作有名管道
  4. 有名管道遵循先进先出规则
  5. 不支持如lseek() 操作 

3.2 函数接口

int mkfifo(const char *filename,mode_t mode);

功能:创健有名管道

参数:filename:有名管道文件名

       mode:权限

返回值:成功:0

       失败:-1,并设置errno号

注意对错误的处理方式:

如果错误是file exist时,注意加判断,如:if(errno == EEXIST)

头文件:

                #include <sys/types.h>
               #include <sys/stat.h>

 

注:函数只是在路径下创建管道文件,往管道中写的数据依然写在内核空间。

先创建有名管道,然后用文件IO操作:打开、读写和关闭。

 3.3 注意事项

  1. 只写方式打开阻塞,一直到另一个进程把读打开
  2. 只读方式打开阻塞,一直到另一个进程把写打开
  3. 可读可写,如果管道中没有数据,读阻塞

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    int fd;
    char buf[32] = "";
    if (mkfifo("fifo", 0666) < 0)
    {
        if (errno == EEXIST)
            printf("file exist!\n");
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo success\n");

    //打开文件
    fd = open("fifo", O_RDWR);
    printf("fd:%d\n", fd);

    //读写操作
    write(fd, "hello", 5);
    read(fd, buf, 32);
    printf("%s\n", buf);
    return 0;
}

练习:通过两个进程实现cp功能。

./input   srcfile

./output  destfile

input.c 源文件

//创建有名管道

//打开管道文件,打开源文件

//循环源文件内容管道

/* src源文件
练习:通过两个进程实现cp功能。
	./input   srcfile
	./output  destfile
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    //创建管道文件
    if (mkfifo("fifo", 0666) < 0)
    {
        if (errno == EEXIST)
        {
            printf("file exist!\n");
        }
        else
        {
            perror("mkfifo err");
            return -1;
        }
    }
    printf("mkfifo success\n");

    char buf[32] = "";
    int n = 0;

    //打开fifo文件
    int fd = open("fifo", O_WRONLY);
    //打开源文件
    int src = open("t3.c", O_RDONLY);
    if (src < 0 || fd < 0)
    {
        perror("open err");
        return -1;
    }

    //循环读源文件写入管道
    while (1)
    {
        while (n = read(src, buf, 32))
        {
            write(fd, buf, n);
        }
        break;
    }

    close(fd);
    close(src);

    return 0;
}
/* dest目标文件
练习:通过两个进程实现cp功能。
	./input   srcfile
	./output  destfile
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/wait.h>

int main(int argc, char const *argv[])
{
    char buf[32] = "";
    int n = 0;

    //打开fifo文件
    int fd = open("fifo", O_RDONLY);
    //打开目标文件
    int dest = open("dest.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd < 0 || dest < 0)
    {
        perror("open err");
        return -1;
    }
    while (1)
    {
        while (n = read(fd, buf, 32))
        {
            write(dest, buf, n);
        }
        break;
    }

    close(fd);
    close(dest);

    return 0;
}

3.4 有名管道无名管道区别

无名管道

有名管道

使用场景

具有亲缘关系进程

不相干两个进程可以

特点

半双工通信方式

固定读端fd[0]写端fd[1]

看作一种特殊文件

通过文件IO操作

文件系统中存在管道文件但是数据放在内核空间

通过文件IO进行操作

遵循先进先出不支持lseek操作

函数

pipe()

直接readwrite

mkfifo()

打开open,读写readwrite

读写特性

管道无数据阻塞

(当关闭写端无数据读立即返回,有数据能读出来)

管道写满阻塞直到4K空间才能继续

关闭读端导致管道破裂

只写方式打开阻塞一直到另一个进程方式打开

只读方式打开苏俄一直到另一个进程方式打开

可读可写如果管道中无数据阻塞

4.信号

kill -l: 显示系统中信号

kill -num PID:指定进程发送信号

4.1 概念

1)信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

2)信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。

3)如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

信号的生命周期:

4.2 信号相应方式

1)忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。

2)捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。

3)执行缺省操作:Linux对每种信号都规定了默认操作 

4.3 信号种类

SIGINT(2):中断信号,Ctrl-C 产生,用于中断进程

SIGQUIT(3):退出信号, Ctrl-\ 产生,用于退出进程并生成核心转储文件

SIGKILL(9):终止信号,用于强制终止进程。此信号不能被捕获或忽略

SIGALRM(14):闹钟信号,当由 alarm() 函数设置的定时器超时时产生。

SIGTERM(15):终止信号,用于请求终止进程。此信号可以被捕获或忽略。termination

SIGCHLD(17):子进程状态改变信号,当子进程停止或终止时产生。

SIGCONT(18):继续执行信号,用于恢复先前停止的进程。

SIGSTOP(19):停止执行信号,用于强制停止进程。此信号不能被捕获或忽略。

SIGTSTP(20):键盘停止信号,通常由用户按下 Ctrl-Z 产生,用于请求停止进程。

4.4 函数接口

4.4.1 信号发送挂起

#include <signal.h>

int kill(pid_t pid, int sig);

功能:给指定进程发送信号

参数:

    pid:指定进程

    sig:要发送的信号

返回值:

    成功:0

    失败:-1

int raise(int sig);

功能:向当前进程发送信号

参数:sig:信号

返回值:

    成功:0

    失败:-1

相当于:kill(getpid(),sig);

int pause(void);

功能:用于将调用进程挂起,直到收到被捕获处理的信号为止。

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

int main(int argc, char const *argv[])
{
    //kill(getpid(),SIGKILL);  //给指定的进程发信号,此例子是给当前进程发信号
    raise(SIGKILL);            //给当前进程发送信号

    //while(1);
    pause();                   //将进程挂起,作用和死循环类似但是不占用CPU

    return 0;
}

 

4.4.2 定时器

unsigned int alarm(unsigned int seconds)

功能:在进程中设置一个定时器。当定时器指定的时间到了时,它就向进程发送SIGALARM信号。系统对SIGALARM信号默认处理方式是退出进程。

参数:seconds:定时时间,单位为秒

返回值:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0

注意:一个进程只能有一个闹钟时间。如果在调用alarm时已设置过闹钟时间,则之前的闹钟时间被新值所代替。

常用操作:取消定时器alarm(0),返回旧闹钟余下秒数。

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

int main(int argc, char const *argv[])
{
    printf("%d\n", alarm(10)); //设置到闹钟10秒后响铃
    sleep(1);
    printf("%d\n", alarm(3));  //重新设置闹钟3秒后响铃,返回上一个闹钟剩余的秒数,以最后一次定闹钟为准
    pause();    //闹钟响后进程直接退出,所以挂起肯定也结束了

    return 0;
}

系统SIGALARM信号响应默认结束进程如果不对闹钟信号进行捕捉默认情况闹钟响了进程直接结束

4.4.3 信号处理函数signal()

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

功能:信号处理函数

参数:signum:要处理的信号

      handler:信号处理方式

          SIG_IGN:忽略信号  (忽略 ignore)

          SIG_DFL:执行默认操作 (默认 default

          handler:捕捉信号 (handler为函数名,可以自定义)

void handler(int sig){} //函数名可以自定义, 参数为要处理的信号

返回值:成功:设置之前的信号处理方式

      失败:-1

补充: typedef 给数据类型重命名

#include <stdio.h>

//给普通数据类型int重命名

typedef int size4;

//给指针类型int* 重命名

typedef int *int_p;

//给数组类型int [10]重命名

typedef int intArr10[10];

//给函数指针void (*)()重命名

typedef void (*fun_p)();

void fun()

{

printf("fun\n");

}

int main(int argc, char const *argv[])

{

    size4 a = 10; //相当于int a

    int_p p = &a; //相当于int* p

    intArr10 arr = {1, 2, 3}; //相当于int arr[10]

    fun_p fp = fun; //相当于 void (* fp)();

printf("%d\n", *p);

printf("%d\n", arr[0]);

fp();

return 0;

}

总而言之,定义变量的变量名写在哪里,用typedef给数据类型重命名的新名字就写在哪里。然后使用新名字定义变量的格式直接就可以为:新名字 变量名;

用信号的知识实现司机和售票员问题。

1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)

2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)

3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)

4)司机等待售票员下车,之后司机再下车。

分析:司机(父进程)、售票员(子进程)

售票: 捕捉SIGINT SIGQUIT SIGUSR1

忽略SIGTSTP

司机 捕捉SIGUSR1 SIGUSR2 SIGTSTP

忽略SIGINT SIGQUIT

 

/*
用信号的知识实现司机和售票员问题。
1)售票员捕捉SIGINT(代表开车)信号,向司机发送SIGUSR1信号,司机打印(let's gogogo)
2)售票员捕捉SIGQUIT(代表停车)信号,向司机发送SIGUSR2信号,司机打印(stop the bus)
3)司机捕捉SIGTSTP(代表到达终点站)信号,向售票员发送SIGUSR1信号,售票员打印(please get off the bus)
4)司机等待售票员下车,之后司机再下车。

分析:司机(父进程)、售票员(子进程)
售票: 捕捉:SIGINT SIGQUIT SIGUSR1
	   忽略:SIGTSTP
司机: 捕捉:SIGUSR1 SIGUSR2 SIGTSTP
	   忽略:SIGINT SIGQUIT
 */

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

pid_t pid;

void saler(int sig)
{
    if(sig == SIGINT)
    {
        kill(getppid(), SIGUSR1);
    }
    else if (sig == SIGQUIT)
    {
        kill(getppid(), SIGUSR2);
    }
    else if (sig == SIGUSR1)
    {
        printf("[saler] please get off the bus\n");
        exit(0);
    }
}

void driver(int sig)
{
    if (sig == SIGUSR1)
    {
        printf("[driver] let's gogogo!\n");
    }
    else if (sig == SIGUSR2)
    {
        printf("[driver] stop the bus!\n");
    }
    else if (sig == SIGTSTP)
    {
        kill(pid, SIGUSR1);
        wait(NULL);
        exit(0);
    }
}

int main(int argc, char const *argv[])
{
    pid = fork();
    if(pid < 0)
    {
        perror("fork err");
        return -1;
    }
    else if (pid == 0)
    {
        printf("i am saler\n");
        signal(SIGINT, saler);
        signal(SIGQUIT, saler);
        signal(SIGUSR1, saler);
        signal(SIGTSTP, SIG_IGN);

    }
    else
    {
        printf("i am driver\n");
        signal(SIGUSR1, driver);
        signal(SIGUSR2, driver);
        signal(SIGTSTP, driver);
        signal(SIGINT, SIG_IGN);
        signal(SIGQUIT, SIG_IGN);
    }
    while(1)
        pause(); //不能只发送一个信号进程就结束,所以可以循环挂起
                 //不占用CPU
    
    return 0;
}

5.共享内存

5.1 特点

1)共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。

2)为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程

将其映射到自己的私有地址空间。进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。

  1. 由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等

5.2 步骤

  1. 创建key值: ftok()
  2. 创建打开共享内存: shmget()
  3. 映射共享内存用户空间: shmat()
  4. 撤销映射:shmdt()
  5. 删除共享内存:shmctl()

5.3 函数接口

key_t ftok(const char *pathname, int proj_id);

功能:创建出来的具有唯一映射关系的一个key值,帮助操作系统用来标识一块共享内存

参数:

    Pathname:已经存在的可访问文件的名字

    Proj_id:一个字符(因为只用低8位)

返回值:成功:key值

      失败:-1

// 将文件的索引节点号取出ls -i,前面加上proj_id得到key_t的返回值。如指定文件的索引节点号为65538,换算成16进制为 0x010002取后4位,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。中间的01是系统编号。

int shmget(key_t key, size_t size, int shmflg);

功能:创建或打开共享内存

参数:

    key  键值

    size   共享内存的大小

    shmflg   IPC_CREAT|IPC_EXCL|0777

返回值:成功  shmid

      出错    -1

//当IPC_CREAT | IPC_EXCL时, 如果没有该块共享内存,则创建,并返回共享内存ID。若已有该块共享内存,则返回-1。

void *shmat(int  shmid,const void *shmaddr,int  shmflg); //attaches

功能:映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问

参数:

    shmid   共享内存的id号

    shmaddr   一般为NULL,表示由系统自动完成映射

              如果不为NULL,那么有用户指定

    shmflg:SHM_RDONLY就是对该共享内存只进行读操作

0     可读可写

返回值:成功:完成映射后的地址,

       出错:-1(地址)

用法:if((= (char *)shmat(shmid,NULL,0)) == (char *)-1)

int shmdt(const void *shmaddr); //detaches

功能:取消映射

参数:要取消的地址

返回值:成功0

      失败的-1

int shmctl(int  shmid,int  cmd,struct shmid_ds *buf); //control

功能:(删除共享内存),对共享内存进行各种操作

参数:

    shmid   共享内存的id号

cmd     IPC_STAT 获得shmid属性信息,存放在第三参数

            IPC_SET 设置shmid属性信息,要设置的属性放在第三参数

            IPC_RMID:删除共享内存,此时第三个参数为NULL即可

   buf    shmid所指向的共享内存的地址,空间被释放以后地址就赋值为null

返回:成功0

     失败-1

用法: shmctl(shmid,IPC_RMID,NULL);

创建key

 

#include <sys/types.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>

int main(int argc, char const *argv[])
{
    key_t key;
    int shmid;
    char *p;

    //创建key值
    key = ftok("shm.c", 'd');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("key: %#x\n", key);

    //创建或打开共享内存
    shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666); //没有则创建共享内存,已有则返回-1

    if (shmid <= 0)
    {
        if (errno == EEXIST)                //如果已存在则直接打开共享内存
            shmid = shmget(key, 128, 0666); //直接打开共享内存,返回共享内存id
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid: %d\n", shmid);

    //映射共享内存到用户空间
    p = (char *)shmat(shmid, NULL, 0);
    if (p == (char *)-1)
    {
        perror("shmat err");
        return -1;
    }

    //操作共享内存
    scanf("%s", p);
    printf("%s\n", p);

   //取消内存映射
   shmdt(p);

   //删除共享内存
   shmctl(shmid,IPC_RMID,NULL);

    return 0;
}

5.4. 命令

ipcs -m: 查看系统中的共享内存

ipcrm  -m  shmid:删除共享内存

ps: 可能不能直接删除掉还存在进程使用的共享内存。

这时候可以用ps -ef对进程进行查看,kill掉多余的进程后,再使用ipcs查看。

练习:两个进程实现通信,一个进程循环从终端输入,另一个进程循环打印,当输入quit时结束

这两个标志在两个进程里,是不共享的,所以为了共享标志位可以和buf封装到一个结构体里作为共享内存。

创建两个文件,一个文件实现循环输入,另一个文件实现循环输出

循环输入

//练习:两个进程实现通信,一个进程循环从终端输入,另一个进程循环打印,当输入quit时结束

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>

struct msg
{
    int flag;
    char buf[32];
};

int main(int argc, char const *argv[])
{
    //创建key值
    key_t key = ftok("t3.txt", 'd');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("key : %#x\n", key);

    //创建或打开共享内存
    int shmid = shmget(key, sizeof(struct msg), IPC_CREAT | IPC_EXCL | 0666);
    //没有则创建共享内存,已有则返回-1
    if (shmid <= 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, sizeof(struct msg), 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid: %d\n", shmid);

    //映射共享内存到用户空间
    struct msg *me = (struct msg *)shmat(shmid, NULL, 0);
    if (me == (struct msg *)-1)
    {
        perror("shmat err");
        return -1;
    }
    me->flag = 0;

    //操作共享内存
    while(1)
    {
        if(0 == me->flag)
        {
            scanf("%s", me->buf);
            me->flag = 1;
        }
        if (strcmp(me->buf,"quit") == 0)
        {
            break;
        }
    }

    //取消内存映射
    shmdt(me);
    
    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    //

    return 0;
}

循环输出

//练习:两个进程实现通信,一个进程循环从终端输入,另一个进程循环打印,当输入quit时结束

#include <sys/types.h>
#include <sys/ipc.h>
#include <stdio.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>

struct msg
{
    int flag;
    char buf[32];
};

int main(int argc, char const *argv[])
{
    //创建key值
    key_t key = ftok("t3.txt", 'd');
    if (key < 0)
    {
        perror("ftok err");
        return -1;
    }
    printf("key : %#x\n", key);

    //创建或打开共享内存
    int shmid = shmget(key, 128, IPC_CREAT | IPC_EXCL | 0666);
    //没有则创建共享内存,已有则返回-1
    if (shmid <= 0)
    {
        if (errno == EEXIST)
            shmid = shmget(key, 128, 0666);
        else
        {
            perror("shmget err");
            return -1;
        }
    }
    printf("shmid: %d\n", shmid);

    //映射共享内存到用户空间
    struct msg *me = (struct msg *)shmat(shmid, NULL, 0);
    if (me == (struct msg *)-1)
    {
        perror("shmat err");
        return -1;
    }

    //操作共享内存
    while(1)
    {
        if(1 == me->flag)
        {
            printf("%s\n", me->buf);
            if (strcmp(me->buf,"quit") == 0)
            {
                break;
            }
            me->flag = 0;
        }
        
    }

    //取消内存映射
    shmdt(me);
    
    //删除共享内存
    shmctl(shmid, IPC_RMID, NULL);
    //

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值