三、消息队列
消息队列是随内核持续,只有在内核重启或者人工删除时,该消息队列才会被删除
①获取键值
消息队列的内核持续性要求每个消息队列都在系统范围内对应唯一的键值,所以,要获得一个消息队列的描述符,必须提供该消息队列的键值。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(char *pathname, char proj);
功能:返回文件名对应的键值。
pathname:文件名
proj:项目名(不为0即可)
②创建打开
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
key: 键值,由ftok获得
msgflg:标志位
返回值:与键值key相对应的消息队列的描述符。
msgflg取值:
IPC_CREAT
创建新的消息队列
IPC_EXCL
与IPC_CREAT一同使用,表示如果要创建的消息队列已经存在,则返回错误。
IPC_NOWAIT
读写消息队列要求无法得到满足时,不阻塞。
在以下两种情况下,将创建一个新的消息队列:
如果没有与键值key相对应的消息队列,并且msgflg中包含了IPC_CREAT标志位。
key参数为IPC_PRIVATE
③发送消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msqid, struct msgbuf * msgp, int msgsz, int msgflg)
功能:向消息队列中发送一条消息
msqid:消息队列描述符
msgp:消息队列指针,指向存放消息的结构
msgsz:消息数据长度
msgflg:发送标志,有意义的msgflg标志为IPC_NOWAIT,指明在消息队列没有足够空间容纳要发送的消息时,msgsnd是否等待
消息格式:
struct msgbuf
{
long mtype; // 消息类型 > 0
char mtext[1]; // 消息数据的首地址
}
④接受消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msqid, struct msgbuf* msgp, int msgsz, long msgtp, int msgflg)
功能:从msqid代表的消息队列中读取一个msgtyp类型的消息,并把消息存储在msgp指向的msgbuf结构中。在成功的读取了一条消息以后,队列中的这条消息将被删除。
⑤队列控制
int msgctl(int msgid, int command, struct msgid_ds *buf);
command是将要采取的动作,它可以取3个值,
IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆盖msgid_ds的值。
IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
IPC_RMID:删除消息队列
buf是指向msgid_ds结构的指针,它指向消息队列模式和访问权限的结构。msgid_ds结构至少包括以下成员:
struct msgid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
};
成功时返回0,失败时返回-1.
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct msgbuf
{
long mtype; /* message type, must be > 0 */
char mtext[256]; /* message data */
};
int main()
{
// 创建消息队列
int msgid = msgget((key_t)1234, 0666|IPC_CREAT);
if (msgid == -1)
{
perror ("msgget");
return -1;
}
//发送消息
struct msgbuf msg;
msg.mtype = 2;
strcpy (msg.mtext, "hello");
int ret = msgsnd(msgid, &msg, 256, IPC_NOWAIT);
if (ret == -1)
{
perror ("nsgsnd");
return -1;
}
//接受消息
struct msgbuf msg;
int ret = msgrcv(msgid, &msg, 256, 2, IPC_NOWAIT);
if (ret == -1)
{
perror ("nsgsnd");
return -1;
}
printf ("%s\n", msg.mtext);
return 0;
}
四、信号通信
①signal
#include <stdio.h>
#include <signal.h>
// 信号处理函数
void handle(int signum)
{
printf ("捕捉到一个信号 %d\n", signum);
}
int main()
{
signal(SIGINT, handle);
signal(SIGTERM, handle);
while (1);
return 0;
}
②kill
#include <stdio.h>
#include <signal.h>
int main()
{
//while (1)
{
kill (18027,9);
sleep(1);
}
return 0;
}
③alarm
#include <stdio.h>
#include <signal.h>
// 信号处理函数
void handle(int signum)
{
printf ("hello world\n");
// 定时器重置
alarm(2);
}
int main()
{
// 定时器是一次性的
alarm(2);
signal(SIGALRM, handle);
while (1);
return 0;
}
④sigaction
#include <stdio.h>
#include <signal.h>
// 信号处理函数
void handle(int signum)
{
printf ("hello world\n");
// 定时器重置
alarm(2);
}
int main()
{
// 定时器是一次性的
alarm(2);
struct sigaction act;
act.sa_handler = handle;
sigaction(SIGALRM, &act, NULL);
while (1);
return 0;
}
⑤处理子进程的退出
#include <stdio.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
// 信号处理函数
void handle(int signum)
{
while (waitpid (-1, NULL, WNOHANG) > 0)
{
printf ("捕获一个子进程\n");
}
printf ("111111\n");
}
int main()
{
signal (SIGCHLD, handle);
int count = 10;
while (count--)
{
pid_t pid = fork();
switch (pid)
{
case -1:
perror ("fork");
break;
case 0: // 子进程
printf ("我是子进程,我的Id 是%d\n", getpid());
sleep(1);
exit(0);
default: // 父进程
printf ("我是父进程,Id = %d\n", getpid());
break;
}
}
while (1);
return 0;
}
五、信号量(进程同步的方式,不是进程间通信方式)
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行进程访问代码的临界区域。临界区域是指执行数据更新的代码需要独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个进程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。
信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。这里主要讨论二进制信号量。
①工作原理
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
②创建
int semget(key_t key, int num_sems, int sem_flags);
第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。
第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。
第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。
semget函数成功返回一个相应信号标识符(非零),失败返回-1.
③操作
它的作用是改变信号量的值:
int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);
第一个参数 sem_id是由semget返回的信号量标识符,
第二个参数 sembuf结构的定义如下:
struct sembuf{
short sem_num;//除非使用一组信号量,否则它为0
short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,
//一个是+1,即V(发送信号)操作。
short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,
//并在进程没有释放该信号量而终止时,操作系统释放信号量
};
第三个参数 nsops:信号操作结构的数量,恒大于或等于1
④控制
该函数用来直接控制信号量信息:
int semctl(int sem_id, int sem_num, int command, ...);
如果有第四个参数,它通常是一个union semum结构,定义如下:
union semun{
int val;
struct semid_ds *buf;
unsigned short *arry;
};
第一个参数是信号量集IPC标识符。
第二个参数是操作信号在信号集中的编号,第一个信号的编号是0
第三个参数 command通常是下面两个值中的其中一个
SETVAL:用来把信号量初始化为一个已知的值。p 这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。
IPC_RMID:用于删除一个已经无需继续使用的信号量标识符。