《嵌入式linux应用程序开发标准教程》笔记——8.进程间通信

, 

8.1 概述

  linux里使用较多的进程间通信方式:

  • 管道,pipe和fifo,管道pipe没有实体文件,只能用于具有亲缘关系的进程间通信;有名管道 named pipe,也叫fifo,还允许无亲缘关系进程间通信;
  • 信号,signal,软件模拟中断的机制,很多信号是系统处理的;
  • 消息队列,messge queue,消息的链表。
  • 共享内存,shared memory,容量大,用的比较多,需要额外的同步机制,如互斥锁和信号量
  • 信号量,semaphore,主要用于进程间的同步和互斥
  • 套接字,socket,主要用于网络通信

8.2 管道

  8.2.1 概述及注意事项

     例如 ps -ef | grep ntp, ps命令的输出,先进入管道(在内核中),grep命令从管道中获取输入数据。

    

   注意事项:

  • 只能用于具有亲缘关系的进程间通信
  • 半双工,读和写端口分开
  • 能用read/write等调用,类似文件,但只存在于内核中
  • 进程创建管道,会创建fds[0]和fds[1],0固定用于读,1固定用于写

  • 子进程继承父进程的管道文件描述符,关闭多余的描述符以后,可以构建父子进程间的通信,兄弟进程也同理。

  •  只有读端存在时,写入才有意义,否则写入端会收到SIGPIPE信号,进程会被终止
  •  缓冲区有空闲,则写入,否则阻塞

  8.2.2 系统调用

#include <unistd.h>

int pipe( int fd[2] );
参数:
  fd[2],管道的文件描述符,0读1写
返回值:
  成功:0
  出错:-1

  

  8.2.3 实例

#include <stdio.h> // printf
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <fcntl.h>

#define PIPE_FD_RD 0
#define PIPE_FD_WR 1

int main(int args, char *argv[])
{
  pid_t pid_fork;
  char buf[32]="pipe data.\n";
  int fd[2];

  if( pipe(fd) < 0 )
  {
    printf("pipe() err,exit.\r\n");
    exit(-1);
  }

  pid_fork = fork();
  if( pid_fork < 0 )
  {
    printf("fork err.\r\n");
  }
  else if( pid_fork > 0 ) // father write
  {
    close(fd[PIPE_FD_RD]);
    while(1)
    {
      printf("Father write data.\n");
      write(fd[PIPE_FD_WR],buf,strlen(buf)+1);
      sleep(1);
    }
  }
  else // child read
  {
    close(fd[PIPE_FD_WR]);
    memset(buf,0,sizeof(buf));
    while(1)
    {
      if( read(fd[PIPE_FD_RD],buf,32 ) > 0 )
      {
        printf("child read data.");
        printf("%s\r\n",buf);
      }
    }
  }

  exit(0);
}

 

  8.2.4 标准流管道 popen/pclose

  标准流把执行另一个程序的流程标准化,简化编程,具体操作如下:

  • 创建一个管道
  • fork一个子进程
  • 在父子进程中关闭不需要的文件描述符
  • 执行exec函数族调用
  • 执行函数中所指定的命令(另一个程序)

  优缺点:

  • 优点:大大减少代码量
  • 缺点:不灵活,只能用标准流读写,不能用read、write这类不带缓冲的IO函数
#include <stdio.h>

FILE * popen( const char * command, const char * type );
参数:
  command:以NULL结尾的字符串,包含shell命令, 会被送到shell中执行。
  type:“r”,读命令的结果,即文件指针连接到命令的标准输出;
     “w”,设置命令的输入,即文件指针连接到命令的标准输入;
返回值:
  成功:文件流指针
  出错:-1

int pclose(FILE * stream);
参数:
  stream:要关闭的文件流
返回值:
  成功:返回由popen所执行进程的退出码
  出错:-1

 

用popen执行ps -ef

/* 8-2,popen,pclose */

#include <stdio.h> // printf,popen/pclose
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <fcntl.h>


int main(int args, char *argv[])
{
  FILE * pfile;
  char buf[1024];

  printf("start.\r\n");
  pfile = popen("ps -ef","r");
  if(pfile == -1)
  {
    printf("popen return err.\r\n");
    exit(-1);
  }
  while( fgets(buf,1024,pfile) !=NULL)  // 注意fgets的参数及返回值
  {
    printf("%s\r\n",buf);
  }  

  rtn = pclose(pfile);            // 获取ps -ef执行的退出码,有若干宏定义可以检测错误,类似wait函数用到的宏定义
  printf("pclose() return 0x%x.\r\n",rtn);

  exit(0);
}

 

  8.2.5 FIFO

  • 有名管道,即FIFO,与普通文件差不多,在文件系统中有实体文件,可以用于互不相关的两个进程实现彼此通信。
  • 不能用lseek()
  • mkfifo()创建有名管道后,可以用open、read、write等函数操作
  • 阻塞问题
    • 读,阻塞打开,若当前FIFO内没有数据,则读进程一直阻塞到有数据写入;
    • 读,非阻塞打开,不管有没有数据,都返回,没有数据时返回0
    • 写,堵塞打开,写操作一直阻塞到可以写入(FIFO有空间了)
    • 写,非阻塞打开,若不能写入全部数据,则写进程进行部分写入或者调用失败
#include <sys/types>
#include <sys/state.h>

int mkfifo( const char * filename, mode_t mode );  // 创建FIFO
参数: 
  filename,要创建的管道名
  mode,与open的第三个参数mode一样,表示该文件的权限
返回值:
  成功,0
  出错,-1

FIFO相关错误总结(errno):
  EACCESS, 参数filename的目录无可执行权限
  EEXIST,参数filename指定的文件已存在
  ENAMETOOLONG,参数filename的路径名称过长
  ENOENT,参数filename包含的目录不存在
  ENOSPC,文件系统的剩余空间不足
  ENOTDIR,目录存在,但不是真正的目录
  EROFS,文件存在于只读文件系统中

 

  8.2.1 FIFO实例

/* 8-3,fifo */

#include <stdio.h> // printf,popen/pclose
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <sys/stat.h>
#include <fcntl.h>

void write_fifo( int args, char *argv[] )
{
  int fd;

  if( args < 2 )
  {
    printf("help: app string_writing_to_fifo.\r\n");
    exit(-1);
  }

  // 创建fifo
  if( access("myfifo",F_OK) < 0)
  {
    if( mkfifo("myfifo",0666)<0 )
    {
      printf("mkfifo error.\r\n");
      exit(-1);
    }
  }

  // open
  if( (fd=open("myfifo",O_WRONLY)) < 0 )
  {
    printf("open fifo err.\r\n");
    exit(-1);
  }

  // write
  if( write(fd,argv[1],128) < 0 )
    printf("write err.\r\n");
}


void read_fifo( void )
{
  int fd;
  char buf[128];

  // open
  if( (fd=open("myfifo",O_RDONLY)) < 0 )
  {
    printf("open fifo err.\r\n");
    exit(-1);
  }

  // read
  if( read(fd,buf,128) < 0 )
    printf("read err.\r\n");
  else
    printf("read from fifo: %s.\r\n",buf);
}

int main(int args, char *argv[])
{

  //write_fifo(args,argv);     // write进程时使用此函数
  read_fifo();            //  read进程是使用此函数
  exit(0);
}

 

$ ./write "hello world"  // 终端1,阻塞,终端2执行read以后接触阻塞,估计是fifo没有读进程时,写不进去

$ ./read           // 终端2,获取fifo中的数据
read from fifo: hello world.

 

8.3 信号

8.3.1 信号概述

  • 用软件对中断机制的一种模拟
  • 进程未处于执行态怎么办?  内核暂时保存信号,待进程进入执行态以后再提交信号
  • 可靠信号和不可靠信号
    • 可靠信号支持排队
    • 不可靠信号不支持排队,一个信号已经注册,又来了一个新的相同信号,操作系统直接扔掉,不排队

  • 信号的处理方式
    • 忽略,两个信号不可忽略,SIGKILL/SIGSTOP
    • 捕捉,自定义信号处理函数,信号发生时,执行响应的处理函数
    • 缺省,系统默认动作,大部分是终止进程
  • linux的常用信号:
    • SIGHUP,终止, 终端连接结束时发出
    • SIGINT,终止, Ctrl+C 时发出,终端发送此信号给每个前台进程;
    • SIGQUIT,终止, Ctrl+\ 时发出;
    • SIGILL,终止, 进程企图执行非法指令
    • SIGFPE,终止, 致命算数运算错误,例如浮点运算错误、溢出、除数为0等
    • SIGKILL,终止, 结束进程,不能被阻塞、处理或忽略
    • SIGALRM,终止, 定时器到时
    • SIGSTOP,暂停,暂停进程,不能被阻塞、处理或忽略
    • SIGTSTP,停止,交互停止进程,Ctrl+Z
    • SIGCHLD,忽略,子进程改变状态,父进程收到此信号
    • SIGABORT,进程异常终止

8.3.2 kill()和raise()

  • kill()可以向进程或进程组发送信号
  • 除了SIGKILL外,还可以发送其他信号;
  • raise()进程给自身发送信号;
#include <signal.h>
#include <sys/types.h>

int kill( pid_t pid, int sig );
参数:
  pid:正数,信号发往的进程号
     0,信号被发往所有与当前进程同一个进程组的进程
     -1,信号发送给所有进程表中的进程
     <-1,信号发送给进程组号为-pid的所有进程
  sig,信号
返回值:
  成功,0
  出错,-1

int raise( int sig );
参数:
  sig,信号
返回值:
  成功,0
  出错,-1

 

 

/* 8-4,kill,raise */

#include <stdio.h> // printf,popen/pclose
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <fcntl.h>
#include <signal.h>


int main(int args, char *argv[])
{
  pid_t pid;

  pid = fork();
  if( pid < 0 )
  {
    printf("fork err.\r\n");
  }
  else if( pid>0 ) // 父进程
  {
    sleep(3);
    printf("kill child %d.\r\n",pid);
    kill(pid,SIGKILL);
    printf("father exit.\r\n");
  }
  else // 子进程
  {
    printf("child %d waiting...\r\n",getpid());
    //raise(SIGSTOP);
    while(1)  // 等待被kill
    {
      printf("child alive.\r\n");
      sleep(1);
    }
  }

  exit(0);
}

$ ./example

child 6442 waiting...
child alive.
child alive.
child alive.
kill child 6442.
father exit.

 

8.3.3 alarm()和pause()

  • alarm()函数是闹钟函数,在进程中设置一个定时器,到时后向进程发送SIGALARM信号;

  【注意】一个进程只能有1个闹钟时间,如果调用alarm()前已经设置过闹钟,则新的闹钟时间取代旧的。

  • pause()将调用进程挂起,直至捕捉到信号为止,常用函数
#include <unistd.h>

unsigned int alarm( unsigned int seconds );
参数:
  seconds,指定定时器秒数,到时后向该进程发送SIGALARM信号
返回值:
  成功,如果之前调用过alarm()函数,则返回上一个闹钟的剩余时间;否则返回0;
  出错,-1

int pause( void )
返回值:-1,并且把errno设置为EINTR.
【注意】只有执行1个信号处理函数并从其返回时, pause才返回
  

 

/* 8-5,alarm/pause */

#include <stdio.h> // printf,popen/pclose
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <fcntl.h>
#include <signal.h>


int main(int args, char *argv[])
{
  printf("start. finish after 5 seconds. \r\n");
  alarm(5);
  pause();

  printf("finish. \r\n"); // 不会执行
  fflush((FILE*)STDOUT_FILENO);

  exit(0);
}

 

$ ./example
start. finish after 5 seconds.   // 5秒以后结束了
闹钟

 

8.3.3 信号的处理

  • 通过接口注册信号处理函数,信号发生时,就会调用注册的信号处理函数了。
  • signal()简单,sigaction()健壮,推荐使用sigaction()函数。
#include <signal.h>

void ( *signal( int signum, void (*handler)(int) ) )(int);
参数:
  signum,信号
  handler,SIG_IGN,忽略该信号
       SIG_DFL,系统默认方式
       自定义的信号处理函数指针
返回值:
  成功,以前的信号处理配置
  出错,-1,NULL

【注意】signal函数形式复杂了,写成如下形式便于理解(就是返回值形式复杂了些):
  typedef void sign( int );
  sign * signal( int , handler *);
  返回值为sign类型,“无返回值并且带一个整型参数的函数”,就是信号的原始配置函数。
  void (*handler)(int),信号处理函数,类似注册一个回调函数,形参int是具体的信号值


int sigaction( int signum, const struct sigaction * act, struct sigaction * oldact );
参数:
  signum,信号,除了SIGKILL和SIGSTOP
  act,指定对特定信号的处理。
  oldact,保存原来对相应信号的处理
返回值:
  成功,0
  出错,-1

【sigaction结构体】
struct sigaction
{
  void (*sa_handler)(int signo);    // 自定义信号处理函数指针/SIG_DEL/SIG_IGN
  sigset_t sa_mask;            // 信号集,指定在信号处理程序执行过程中哪些信号应当被屏蔽
  int sa_flags;              // 标志位,包含若干对信号进行处理的各个选择项
  void (*sa_restore)(void);       //
}

sa_flags:
SA_NODEFER/SA_NOMASK,当捕捉到此信号时,在执行其信号捕捉函数时,系统不会自动屏蔽此信号
SA_NOCLDSTOP,进程忽略子进程产生的任何SIGSTOP/SIGTSTP/SIGTTI/SIGTTOU信号
SA_RESTART,令重启的系统调用起作用
SA_ONESHOT/SA_RESETHAND,自定义信号只执行1次,执行完毕后恢复信号的系统默认动作

 

/* 8-6,signal */

#include <stdio.h> // printf,popen/pclose
#include <stdlib.h> // exit
#include <unistd.h>
#include <sys/types.h> // pid_t
#include <fcntl.h>
#include <signal.h>


void fun_sigal( int sig_no )
{
  if( sig_no == SIGINT )
    printf("Signal SIGINT\r\n");
  else if( sig_no == SIGQUIT )
    printf("Signal SIGQUIT\r\n");
  else
    printf("Other SIGQUIT\r\n");
}

int main(int args, char *argv[])
{
  printf("start. \r\n");

  signal(SIGINT,fun_sigal);
  signal(SIGQUIT,fun_sigal);

  pause(); // 从信号处理函数返回
  pause(); // 再等待一次
  printf("finish. \r\n");

  exit(0);
}

$ ./example
start.
^CSignal SIGINT
^CSignal SIGINT    // 相同信号,多次发生的情况
finish.

 

$ ./example
start.
^CSignal SIGINT
^\Signal SIGQUIT
finish.

 

 

/* 8-7,sigaction */

#include <stdio.h>    // printf,popen/pclose
#include <stdlib.h>    // exit
#include <unistd.h>
#include <sys/types.h>    // pid_t
#include <fcntl.h>
#include <signal.h>


void fun_sigal( int sig_no )
{
    if( sig_no == SIGINT )
        printf("Signal SIGINT\r\n");
    else if( sig_no == SIGQUIT )
        printf("Signal SIGQUIT\r\n");
    else
        printf("Other SIGQUIT\r\n");
}

int main(int args, char *argv[])
{
    struct sigaction st_sigact;

    printf("start. \r\n");
    
    st_sigact.sa_handler = fun_sigal;
    st_sigact.sa_flags=0;
    sigemptyset(&st_sigact.sa_mask);

    sigaction(SIGINT,&st_sigact,NULL);
    sigaction(SIGQUIT,&st_sigact,NULL);

    pause();    // 从信号处理函数返回
    pause();    // 再等待一次
    printf("finish. \r\n");

    exit(0);
}

【执行效果与signal例程相同】

 

 

8.3.4 信号集

  后续补充吧。

8.4 信号量

8.4.1 信号量概述

  • 不同进程有可能争抢共享资源
  • 信号量用来解决进程之间的同步和互斥,信号量包括
    • 信号量变量,代表当前可用的该资源的数量,>0表示资源可用, <=0表示无可用资源;一般信号量只有0/1,叫二维信号量。
    • 在该信号量下等待资源的进程等待队列
    • 对信号量的两个原子操作,PV
      • P操作,占用资源,-1,如果没有可用资源,则被阻塞,直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该资源
      • V操作,释放资源,+1,如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程

创建信号量,semget(),不同进程用相同键值获取同一个信号量
初始化,semctl(),SETVAL
PV操作,semop()
删除,semctl(), IPC_RMID

8.4.2 函数

#include <sys/types>
#include <sys/ipc.h>
#include <sys/sem.h>

int semget( key_t key, int nsems, int semflg );    // 获取或创建信号量
参数:  
  key,信号量的键值,多个进程通过同一个键值访问同一个信号量;IPC_PRIVATE用于创建本进程的私有信号量;
  nsems,需创建的信号量数目,通常取值为1
  semflg,类似open函数的flg+mode的集合,其中权限与open相同,也可用八进制表示;IPC_CREAT,创建,已存在也不出错;IPC_EXCL,如果创建时已经存在,则返回错误;
返回值:
  成功,信号量标识符,类似文件描述符
  出错,-1

int semctl( int semid, int semnum, int cmd, union semun arg );
参数:
  semid,信号量标识符
  semnum,信号量编号,一般为0,使用信号量集时才有用
  cmd,指定各种操作,常用如下:
    IPC_STAT,获取信号量的状态结构体semid_ds,并存放在第四个参数arg的buf中。semid_ds是系统中描述信号量的数据结构;
    IPC_SET,将信号量的值设置为arg的val
    IPC_GET,返回信号量的当前值
    IPC_RMID,删除信号量
  arg,union semun结构,在有些系统里需要自己定义,linux里需要自己定义
    union semun
    {
      int val;
      struct semid_ds * buf;
      unsigned short * array;
    }
返回值:
  成功:IPC_STAT/IPC_SETVAL/IPC_RMID时,返回0
     IPC_GETVAL时返回信号量当前值
  出错:-1

int semop( int semid, struct sembuf * sops, size_t nsops );
参数:
  semid,信号量标识符
  sops,指向信号量操作结构体数组
    struct sembuf
    {
      short sem_num;  // 信号量编号,使用单个信号量时,通常取值为0
      short sem_op;   // -1为P操作,+1为V操作
      short sem_flg;  // 通常设置为SEM_UNDO,这样进程没有释放信号量就退出时,系统自动释放该信号量
    }
  nsops,该数组的操作个数,通常取1
返回值:
  成功,0
  出错,-1

8.4.3 例程

/* 8-8,sem */

#include <stdio.h>    // printf,popen/pclose
#include <stdlib.h>    // exit
#include <unistd.h>
#include <sys/types.h>    // pid_t
#include <fcntl.h>
#include <sys/sem.h>
#include <sys/ipc.h>

union semun
{
    int val;
    struct semid_ds * buf;
    unsigned short * array;
};

void sem_init( int semid, int val )
{
    union semun arg;

    arg.val = val;
    semctl( semid, 0, IPC_SET, arg);
}

void sem_p( int semid)
{
    struct sembuf st_sem_buf;
    
    st_sem_buf.sem_num = 0;
    st_sem_buf.sem_op = -1;
    st_sem_buf.sem_flg = SEM_UNDO;
    semop( semid, &st_sem_buf ,1 );
}

void sem_v( int semid )
{
    struct sembuf st_sem_buf;
    
    st_sem_buf.sem_num = 0;
    st_sem_buf.sem_op = 1;
    st_sem_buf.sem_flg = SEM_UNDO;
    semop( semid, &st_sem_buf ,1 );
}

void sem_del( int semid )
{
    union semun arg;

    semctl( semid, 0, IPC_RMID, arg);    
}


int main(int args, char *argv[])
{
    pid_t pid;    
    key_t key;
    int semid;

    printf("start. \r\n");
    
    // creat sem
    key = ftok(".",0);
    if( (semid=semget(key,1,0666|IPC_CREAT)) < 0 )
    {
        printf("semget err.\r\n");
        exit(-1);
    }
    
    sem_init(semid,0);        

    pid = fork();
    if( pid > 0 )    // 父进程
    {
        sleep(5);
        printf("father process.\r\n");
        sem_v(semid);
    }    
    else if( pid == 0 )
    {
        sem_p(semid);
        printf("child process.\r\n");
        sem_v(semid);
    }
    else
    {
        printf("fork err.\r\n");
        exit(-1);
    }

    printf("finish. \r\n");

    exit(0);
}

$ ./example
start.           // 5秒后后面打印信息出现
father process.    
finish.
child process.
finish.

【注意】此例用sem实现父子进程顺序执行,属于同步的例子。 sem也可以实现互斥操作。

8.5 共享内存

8.5.1 概述

  • 内核有专门的内存区用于进程间共享,进程可以将其映射到自己的私有地址空间后访问。
  • 数据读写高效,不需要额外复制,但是需要额外的同步和互斥机制
  • 【注意】  linux命令ipcs可以查看共享内存、消息队列的各种进程间通信机制的情况

 

8.5.2 函数

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

int shmget( key_t key, int size, int shmflg );  // 从内核中获取共享内存
参数:
  key,共享内存的键值,与信号量函数中的键值相同
  size,共享内存大小
  shmflg,同open的权限位,可用8进制表示
返回值:
  成功:共享内存段标识符
  出错,-1

int shmat( int shmid, const void * shmaddr, int shmflg );  // 映射从内核中获取的共享内存,映射已后进程就可以使用了
参数:
  shmid,共享内存标识符
  shmaddr,将共享内存映射到指定地址,若为0,则系统自动分配
  shmflg,SHM_RDONLY,只读;默认0,可读写
返回值:
  成功,被映射的段地址
  出错,-1

int shmdt( const void * shmaddr );
参数:
  shmaddr,被映射的共享内存段地址
返回值:
  成功,0
  出错,-1

8.5.3 例程

/* 8-9,shm */

#include <stdio.h>    // printf,popen/pclose
#include <stdlib.h>    // exit
#include <string.h>
#include <unistd.h>
#include <sys/types.h>    // pid_t
#include <fcntl.h>
//#include <sys/sem.h>
#include <sys/ipc.h>
#include <sys/shm.h>


int main(int args, char *argv[])
{
    pid_t pid;    
    int shmid;
    char * shm_addr;
    char buf[128];
    char flg[6]="write";

    printf("start. \r\n");
    
    // creat sem
    if( (shmid=shmget(IPC_PRIVATE,128,0666)) < 0 )
    {
        printf("semget err.\r\n");
        exit(-1);
    }
    else
        printf("semget ok,shmid %d\r\n",shmid);

    system("ipcs -m");
    
    pid = fork();
    if( pid > 0 )    // 父进程
    {
        if( (shm_addr=(char*)shmat(shmid,0,0) ) == ((void*)-1) )    
        {    
            printf("shmat err in father process,rtn 0x%x\r\n",shm_addr);
            exit(-1);
        }
        else
            printf("shmat addr 0x%x in father process.\r\n",shm_addr);

        sleep(1);
        printf("input something in father.\r\n");
        fgets(buf,128,stdin);    
        strncpy(shm_addr,flg,strlen(flg));
        strncpy(shm_addr+strlen(flg),buf,strlen(buf));        

        printf("father finish.write data:%s \r\n",shm_addr);
    }    
    else if( pid == 0 )
    {
        printf("child pid %d\r\n",getpid());
        if( (shm_addr=(char*)shmat(shmid,0,0) ) == ((void*)-1) )    
        {    
            printf("shmat err in child process,rtn 0x%x\r\n",shm_addr);
            exit(-1);
        }
        else
            printf("shmat addr 0x%x in child process.\r\n",shm_addr);    

        printf("\r\nchild read shm:%s \r\n",shm_addr);
        while(strncmp(flg,shm_addr,strlen(flg)) != 0)
        {
            sleep(1);
            printf("\r\nchild read shm:%s \r\n",shm_addr);
        }
        printf("rev from father process:%s\r\n",shm_addr+strlen(flg));

        shmdt(shm_addr);
        printf("child finish. \r\n");
    }
    else
    {
        printf("fork err.\r\n");
        exit(-1);
    }

    exit(0);
}

【运行结果】

shmat addr 0xb3cad000 in father process.
child pid 10449
shmat addr 0xb3cad000 in child process.


child read shm:
input something in father


child read shm:
12
father finish.write data:write12

$
child read shm:write12

rev from father process:12

child finish.



 

8.6 消息队列

8.6.1 概述

  • 消息队列是消息的列表,用户可以添加和读取消息
  • 比FIFO功能多一些,用户可以随机查询消息
  • 消息存在于内核中,由“队列ID”标识
  • 有点类似通信类接口,以包为单位

8.6.2 函数

  • msgget(),创建或打开消息队列
  • msgsnd(), 添加消息队列
  • msgrcv(),读取消息队列,可以指定取走某一条消息
  • msgctl(), 控制消息队列
#include <sys/types>
#include <sys/ipc.h>
#include <sys/shm.h>

int msgget( key_t key, int msgflg );  
参数:
  key, 消息队列键值,多进程共享时使用,特殊键值IPC_PRIVATE表示当前进程的私有消息队列
  msgflg, 权限标志位
返回值:
  成功,消息队列ID
  出错,-1

int msgsnd( int msqid, const void * msgp, size_t msgsz, int msgflg );
参数:
  msqid,消息队列ID
  msgp,指向消息结构的指针,该结构通常为
    struct msgbuf
    {
      long mtype;    // 消息类型,同一个消息队列,可以有多种消息类型,接收时可以按类型接收,这点比较方便
      char mtext[1];  // 消息正文
    }
    【注意】该结构为模板,实际使用时需要自己根据数据量进行定义
  msgsz,消息正文的字节数,不包括消息类型
  msgflg,IPC_NOWAIT,不阻塞;0,阻塞直到发送成功为止
返回值:
  成功,0
  出错,-1

int msgrcv( int msqid, void * msgp, size_t msgsz, long int msgtyp, int msgflg );
参数:
  msqid,消息队列ID
  msgsz,消息缓冲区,同msgsnd的msgp
  msgtyp, 0,接收消息队列中的第一个消息,不管类型了
       大于0,接收第一个类型为msgtyp的消息
       小于0,接收第一个类型“不小于msgtyp绝对值”且“类型值最小”的消息
  msgflg,MSG_NOERROR,若返回的消息比msgsz字节多,则消息就会截短到msgsz字节,且不通知消息发送进程
      IPC_NOWAIT,不阻塞
     0,阻塞,直到收到一条消息为止
返回值:
  成功,0
  出错,-1

int msgctl( int msqid, int cmd, struct msqid_ds * buf );
参数:
  msqid,消息队列的队列ID
  cmd,IPC_STAT,读取消息队列的数据结构msqid_ds,并将其保存在buf中
     IPC_SET,设置消息队列数据结构msqid_ds中的ipc_perm域(IPC操作权限描述结构),该值取自buf
     IPC_RMID,删除消息队列
  buf,描述消息队列的msqid_ds结构类型变量
返回值:
  成功,0
  出错,-1

 

8.6.3 例程

 

posted @ 2017-04-11 15:20  liuwanpeng  阅读(651)  评论(0编辑  收藏  举报