《嵌入式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 例程