UNIX System V IPC 进程间共享数据的方法有三种:
这三种方式如果没有设置 IPC_CREAT 标志并且还未创建 IPC 实例,则 get 调用将返回错误。
消息队列
消息队列的好处是一个进程发送完后就可以结束了,接收进程接收消息并释放消息可以是好几天之后了。接收时还可以指定消息的类型。
msg_server.c msg_client.c
无论谁先启动都会创建消息队列,后启动的get创建的消息队列。之后msg_server 发送消息,msg_client 读取相应消息类型并释放消息队列。
如果msg_server 先启动,则msg_server进程立即结束,之后msg_client进程读取消息并释放消息队列。
msg_server.c
#include
#include
#include
#include
#include
int main (void)
{
key_t ipckey;
int mq_id;
struct { long type; char text[100]; } mymsg;
/* Generate the ipc key */
ipckey = ftok("/tmp", 42);
printf("My key is %d\n", ipckey);
/* Set up the message queue */
mq_id = msgget(ipckey, IPC_CREAT | 0660);
printf("Message identifier is %d\n", mq_id);
/* Send a message */
memset(mymsg.text, 0, 100); /* Clear out the space */
strcpy(mymsg.text, "Hello, world!");
mymsg.type = 3;
printf("%d\n", sizeof(mymsg));
msgsnd(mq_id, &mymsg, sizeof(mymsg), 0);
return 0;
}
msg_client.c
#include
#include
#include
#include
#include
#include
int main (void)
{
key_t ipckey;
int mq_id;
struct { long type; char text[100]; } mymsg;
int received;
/* Generate the ipc key */
ipckey = ftok("/tmp", 42);
printf("My key is %d\n", ipckey);
/* Set up the message queue */
mq_id = msgget(ipckey, IPC_CREAT | 0660);
if (mq_id == -1)
{
perror("msgget error");
exit(-1);
}
printf("Message identifier is %d\n", mq_id);
received = msgrcv(mq_id, &mymsg, sizeof(mymsg), 3, 0);
printf("%s (%d)\n", mymsg.text, received);
msgctl(mq_id, IPC_RMID, NULL);
return 0;
}
msgsnd() 第四个参数指示是否阻塞该调用。
如果该参数标志为 IPC_NOWAIT,则即使队列已满,该调用也会返回。如果该参数标志为 0,则调用将阻塞,直至队列上的空间被释放、队列被删除或应用程序收到某个信号。
msgrcv( ) 系统调用是先由核心检查消息队列标识符和许可权,接着根据msgtyp分三种情况处理。
(1) msgtyp=0,核心寻找消息队列中的第一个消息,并将它返回给调用进程;
(2)msgtyp为正整数,核心返回给类型的第一个消息;
(3)msgtyp为负整数,核心应在其类型值小于或等于msgtyp绝对值的所有消息中,选择类型最低的第一消息返回。
如果所返回的消息的大小等于或小于用户请求,核心便将消息正文拷贝到用户区,再从队列中删除该消息,并唤醒睡眠的发送进程;如果消息比用户要求的大,则系统返回错误信息。
IPC_NOWAIT 如果没有满足条件的消息,调用立即返回,此时,errno=ENOMSG
IPC_EXCEPT 与msgtyp>0配合使用,返回队列中第一个类型不为msgtyp的消息
IPC_NOERROR 如果队列中满足条件的消息内容大于所请求的msgsz字节,则把该消息截断,截断部分将丢失。
消息队列对于短期进程是有用的。
共享内存
创建共享内存、写共享内存均需root权限,创建后内存区域所有数据均为0。如果写后读一次,共享内存区里面的数据全变为0,通常是由于读后释放共享内存,而再次读的是其他的共享内存区域。
ipc_shm_write.c ipc_shm_read.c
创建共享内存,系统会以页为单位进行分配,所以程序里传入4096 当作参数(一页)。无论谁先启动,都会创建共享内存页。但如果是ipc_shm_read.c 先创建,他自身结束时又会释放创建的共享内存。
ipc_shm_write.c
#include
#include
#include
#include
#include
#include
typedef struct {
char name;
int age;
} people;
int main(int argc, char **argv)
{
int shm_id, i;
key_t key;
people *p_map = NULL;
char temp_char = 'a';
char *name = "/dev/shm";
key = ftok(name, 0);
printf("key=%d\n", key);
if (key == -1) {
perror("ftok error");
return -1;
}
shm_id = shmget(key, 4096, IPC_CREAT);
printf("shm_id=%d\n", shm_id);
//if (shm_id == -1 && errno == EEXIST);
if (shm_id == -1) {
perror("shmget error");
return -1;
}
p_map = (people *) shmat(shm_id, NULL, 0);
if (p_map == NULL) {
perror("shmat error");
return -1;
}
for (i = 0; i < 10; i++) {
//(*(p_map + i)).name = temp_char;
(p_map + i)->name = temp_char;
(*(p_map + i)).age = 20 + i;
printf("%c\n", temp_char);
temp_char += 1;
}
if (shmdt(p_map) == -1) {
perror("detach error");
return -1;
}
return 0;
}
ipc_shm_read.c
#include
#include
#include
#include
#include
typedef struct {
char name;
int age;
} people;
int main(int argc, char **argv)
{
int shm_id, i;
key_t key;
people *p_map = NULL;
struct shmid_ds shmbuf;
char *name="/dev/shm";
key = ftok(name, 0);
printf("key=%d\n", key);
if (key == -1) {
perror("ftok error");
return -1;
}
shm_id = shmget(key, 4096, IPC_CREAT);
printf("shm_id=%d\n",shm_id);
p_map = (people *) shmat(shm_id, NULL, 0);
if (p_map == NULL) {
perror("shmat error");
return -1;
}
for (i = 0; i < 10; i++) {
printf("name----------%c\n", (*(p_map + i)).name);
printf("age------------%d\n", (*(p_map + i)).age);
}
if (shmdt(p_map) == -1) {
perror("shmdt error");
return -1;
}
if (shmctl(shm_id, IPC_RMID, &shmbuf) < 0) {
perror("shmctl error");
return -1;
}
return 0;
}
shmget(IPC_PRIVATE,...) IPC 密钥使用了 IPC_PRIVATE
当使用了 IPC_PRIVATE 时,将保证创建一个唯一的 IPC ID,并且预期应用程序将自己分发该 ID。
shmat 需要共享内存 ID、一个指针和某些标志。
该指针用于请求特定的内存地址。通过传递 0,内核可以随心所欲地选择任何内存地址。标志大部分是特定于供应商的,不过 SHM_RDONLY 是一个公共标志,用于指示不写入的段。shmat 的常见用法是让内核决定一切。
信号量
信号量+共享内存模型,可以用来使两个进程之间频繁通信,更棒的是不使用锁就可以完成。
sem_server.c sem_client.c
如果sem_client.c 无法get到信号量,就阻塞在那,并以0.1ms的粒度不停检测sem_server.c 是否创建(这个粒度的检测在Intel Pentium 1.7GHz的CPU上耗费4%的使用率)。这个例子的P, V 操作也是经典的实现。
在Linux下需要定义union semun(linux下头文件在linux/sem.h),而FreeBSD下却不需要(在sys/sem.h)。
C语言:
sem_server.c
#include
#include
#include
#include
#include
#include
#include
#include
#define SEGSIZE 1024
#define READTIME 1
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
int generate_sem(key_t key)
{
union semun sem;
int semid;
sem.val = 0;
semid = semget(key, 1, IPC_CREAT | 0666);
if (semid == -1) {
perror("create semaphore error\n");
exit(-1);
}
/* 初始化信号量 */
//semctl(semid, 0, SETVAL, sem);
return semid;
}
void del_sem(int semid)
{
//union semun sem;
//sem.val = 0;
semctl(semid, 0, IPC_RMID, 0);
}
int p(int semid)
{
struct sembuf sops = { 0, 1, IPC_NOWAIT };
return (semop(semid, &sops, 1));
}
int v(int semid)
{
struct sembuf sops = { 0, -1, IPC_NOWAIT };
return (semop(semid, &sops, 1));
}
int main()
{
key_t key;
int shmid, semid;
char *shmaddr;
char msg[7] = "data ";
char i;
struct shmid_ds shmbuf;
key = ftok("/", 0);
shmid = shmget(key, SEGSIZE, IPC_CREAT | 0604);
if (shmid == -1) {
printf("create shared momery error\n");
return -1;
}
shmaddr = (char *) shmat(shmid, NULL, 0);
if (shmaddr == (void *) -1) {
printf(" attach shared momery error\n");
return -1;
}
semid = generate_sem(key);
for (i = 0; i <= 3; i++) {
p(semid);
sleep(READTIME);
msg[5] = 'a' + i;
memcpy(shmaddr, msg, sizeof(msg));
sleep(2);
v(semid);
}
shmdt(shmaddr);
shmctl(shmid, IPC_RMID, &shmbuf);
del_sem(semid);
return 0;
}
sem_client.c
#include
#include
#include
#include
#include
#include
#include
#include
#define SEGSIZE 1024
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
/* 打印程序执行时间 */
void secondpass()
{
static long start = 0;
time_t timer;
if (start == 0) {
timer = time(NULL);
start = (long) timer;
}
printf("second:%ld\n", (long) (time(NULL)) - start);
}
int get_sem(key_t key)
{
union semun sem;
int semid;
sem.val = 0;
semid = semget(key, 0, 0);
if (semid == -1) {
return -1;
}
return semid;
}
/* 等待信号量变成 0 */
void waitv(int semid)
{
/* {1, 0, 0} 将会阻塞到这里,但CPU使用率还是那么多 */
struct sembuf sops = { 0, 0, 0 };
semop(semid, &sops, 1);
}
int main()
{
key_t key;
int shmid, semid;
char *shmaddr;
int i;
key = ftok("/", 0);
shmid = shmget(key, SEGSIZE, IPC_CREAT | 0604);
if (shmid == -1) {
printf("get shared momery error\n");
return -1;
}
shmaddr = (char *) shmat(shmid, NULL, 0);
if (shmaddr == (void *)-1) {
printf("client attach shared momery error\n");
return -1;
}
while ( (semid = get_sem(key)) == -1 ) {
//printf("get semaphore error\n");
usleep(100);
}
for (i = 0; i < 3; i++) {
sleep(1);
waitv(semid);
printf("the msg get is: %s\n", shmaddr);
secondpass();
}
shmdt(shmaddr);
return 0;
}
semget() 第二个参数指定信号量集(Semaphore Set) 的大小。
信号量集是一组共享一个公共 IPC 实例的信号量。该集合中的信号量数量无法更改。如果已经创建了信号量集,则 semget 的第二个参数实际上被忽略。
sem_op in struct sembuf
(1)如果 sem_op 为 0,则测试 sem_num 以确定它是否为 0。如果 sem_num 为 0,则运行下一个测试。如果 sem_num 不为 0,则在未设置 IPC_NOWAIT 时,操作将阻塞直至信号量变为 0,而在设置了 IPC_NOWAIT 时,则跳过其他测试。
(2)如果 sem_op 是某个正数,则将信号量的值加上 sem_op 的值。
(3)如果 sem_op 是一个负整数,并且信号量的值大于或等于 sem_op 的绝对值,则从信号量的值减去该绝对值。
(4)如果 sem_op 是一个负整数,并且信号量的值小于 sem_op 的绝对值,则在 IPC_NOWAIT 为 true 时立即停止测试的执行,而在该值为 false 时则阻塞,直至信号量的值变得大于 sem_op 的绝对值。
信号量被称为建议锁(Advisory Lock)。这意味着信号量本身并不阻止两个进程同时使用同一个资源;相反,它们旨在建议任何进程自愿询问该资源是否正在使用。
参考链接:
(信号量代码)
阅读(1298) | 评论(0) | 转发(0) |