进程间通信(2)
共享内存
共享内存是指多个进程看到同一块内存。
当然这块内存由操作系统维护。
需要通信的进程通过操作系统看到这块内存。
简单示意图
共享内存相关函数
shmget函数
功能:用来创建共享内存
原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字
size:共享内存大小
shmflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该共享内存段的标识码;失败返回-1
shmat函数
功能:将共享内存段连接到进程地址空间
原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值:成功返回一个指针,指向共享内存第一个节;失败返回-1
说明:
shmaddr为NULL,核心自动选择一个地址
shmaddr不为NULL且shmflg无SHM_RND标记,则以shmaddr为连接地址。
shmaddr不为NULL且shmflg设置了SHM_RND标记,则连接的地址会自动向下调整为SHMLBA的整数倍。公式:shmaddr - (shmaddr % SHMLBA)
shmflg=SHM_RDONLY,表示连接操作用来只读共享内存
shmdt函数
功能:将共享内存段与当前进程脱离
原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值:成功返回0;失败返回-1
注意:将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能:用于控制共享内存
原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作(有三个可取值)
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构
返回值:成功返回0;失败返回-1
使用共享内存实现进程通信
//Makefile
.PHONY:all
all: server client
server: server.c comm.c
gcc -o $@ $^
client: client.c comm.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -f server client
//comm.h
#ifndef _COMM_H_
#define _COMM_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#define PATH "./"
#define FTOK_ID 0x333
int createshm(int size);
int getshm(int size);
int destroyshm(int shmid);
#endif
//comm.c
#include "comm.h"
static int comm(int size,int flag)
{
key_t key = ftok(PATH, FTOK_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int shmid = shmget(key, size , flag);
if(shmid < 0)
{
perror("shmid");
return -2;
}
return shmid;
}
int createshm(int size)
{
return comm(size, IPC_CREAT|IPC_EXCL);
}
int getshm(int size)
{
return comm(size, IPC_CREAT);
}
int destroyshm(int shmid)
{
return shmctl(shmid, IPC_RMID, NULL);
}
//server.c
#include "comm.h"
int main()
{
int shmid = createshm(4096);
char* addr = shmat(shmid, NULL, 0);
if(addr == NULL)
{
perror("shmat");
return 1;
}
int i = 0;
while(i < 30)
{
printf("%s\n", addr);
i++;
sleep(1);
}
shmdt(addr);
destroyshm(shmid);
return 0;
}
//client.c
#include "comm.h"
int main()
{
int shmid = getshm(4096);
char* addr = shmat(shmid, NULL, 0);
if(addr == NULL)
{
perror("shmat");
return 1;
}
int i = 0;
while(i < 30)
{
addr[i++] = 'A';
addr[i] = '\0';
sleep(1);
}
shmdt(addr);
return 0;
}
运行结果
查看/删除共享内存的命令
ipcs -m / ipcrm -m id
共享内存的特点:
它是最快的进程间通信方式,但没有同步互斥机制
生命周期随内核,与消息队列一致
信号量
信号量机制是一种进程同步工具
信号量本质上是一个计数器
信号量结构体伪代码
struct semaphore
{
int value;
pointer_PCB queue;
}
value 表示的是资源数目
queue 表示的是需要获取资源的进程队列
p操作
将资源数目减1
v操作
将资源数目加1
P原语
P(s)
{
s.value = s.value--;
if (s.value < 0)
{
该进程状态置为等待状状态
将该进程的PCB插入相应的等待队列s.queue末尾
}
}
V原语
V(s)
{
s.value = s.value++;
if (s.value < =0)
{
唤醒相应等待队列s.queue中等待的一个进程
改变其状态为就绪态
并将其插入就绪队列
}
}
信号量集函数
semget函数
功能:用来创建和访问一个信号量集
原型
int semget(key_t key, int nsems, int semflg);
参数
key: 信号集的名字
nsems:信号集中信号量的个数
semflg: 由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该信号集的标识码;失败返回-1
shmctl函数
功能:用于控制信号量集
原型
int semctl(int semid, int semnum, int cmd, …);
参数
semid:由semget返回的信号集标识码
semnum:信号集中信号量的序号
cmd:将要采取的动作(有三个可取值)
最后一个参数根据命令不同而不同
返回值:成功返回0;失败返回-1
semop函数
功能:用来创建和访问一个信号量集
原型
int semop(int semid, struct sembuf *sops, unsigned nsops);
参数
semid:是该信号量的标识码,也就是semget函数的返回值
sops:是个指向一个结构数值的指针
nsops:信号量的个数Copyright 2017 比特科技
比特科技
返回值:成功返回0;失败返回-1
说明:
sembuf结构体:
struct sembuf {
short sem_num;
short sem_op;
short sem_flg;
};
sem_num是信号量的编号。
sem_op是信号量一次PV操作时加减的数值,一般只会用到两个值:
一个是“-1”,也就是P操作,等待信号量变得可用;
另一个是“+1”,也就是我们的V操作,发出信号量已经变得可用
sem_flag的两个取值是IPC_NOWAIT或SEM_UNDO
使用二元信号量可以保护临界资源
//Makefile
.PHONY:sem clean
sem:test_sem.c comm.c
gcc -o $@ $^
clean:
rm -f sem
//comm.h
#ifndef __COMM_H_
#define __COMM_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define PATH "./"
#define SEM_ID 0x3333
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
int createsem(int nsems);
int getsem(int nsems);
int destroysem(int semid);
int initsem(int semid, int nu, int value);
int P(int semid, int nu);
int V(int semid, int nu);
#endif
//comm.c
#include "comm.h"
static int comm(int nsems, int flag)
{
key_t key = ftok(PATH, SEM_ID);
if(key < 0)
{
perror("ftok");
return -1;
}
int semid = semget(key, nsems, flag);
if(semid < 0)
{
perror("semget");
return -2;
}
return semid;
}
int createsem(int nsems)
{
return comm(nsems, IPC_CREAT|IPC_EXCL);
}
int getsem(int nsems)
{
return comm(nsems, IPC_CREAT);
}
int initsem(int semid, int nu, int value)
{
union semun _un;
_un.val = value;
if(semctl(semid, nu, SETVAL, _un) < 0)
{
perror("semctl");
return -1;
}
return 0;
}
static int commPV(int semid, int nu, int op)
{
struct sembuf _s;
_s.sem_num = nu;
_s.sem_op = op;
_s.sem_flg = 0;
if(semop(semid, &_s, 1) < 0)
{
perror("semop");
return -1;
}
return 0;
}
int P(int semid, int nu)
{
return commPV(semid, nu, -1);
}
int V(int semid, int nu)
{
return commPV(semid, nu, 1);
}
int destroysem(int semid)
{
if(semctl(semid, 0, IPC_RMID) < 0)
{
perror("semctl");
return -1;
}
return 0;
}
//sem.c
#include "comm.h"
int main()
{
int semid = createsem(1);
initsem(semid, 0, 1);
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return 1;
}
else if(pid == 0)
{
//child
int _semid = getsem(1);
int i = 0;
while(i++ < 20)
{
P(_semid, 0);
printf("A");
fflush(stdout);
usleep(218723);
printf("A ");
fflush(stdout);
usleep(322109);
V(_semid, 0);
}
}
else
{
//father
int i = 0;
while(i++ < 20)
{
P(semid, 0);
printf("B");
fflush(stdout);
usleep(218732);
printf("B ");
fflush(stdout);
usleep(321312);
V(semid ,0);
}
wait(NULL);
destroysem(semid);
}
return 0;
}
此时屏幕成为临界资源,可以看到AA BB 成对出现 如果去掉 P、V呢?
#include "comm.h"
int main()
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return 1;
}
else if(pid == 0)
{
//child
int i = 0;
while(i++ < 20)
{
printf("A");
fflush(stdout);
usleep(218723);
printf("A ");
fflush(stdout);
usleep(322109);
}
}
else
{
//father
int i = 0;
while(i++ < 20)
{
printf("B");
fflush(stdout);
usleep(218732);
printf("B ");
fflush(stdout);
usleep(321312);
}
wait(NULL);
}
return 0;
}
看到AA, BB 不会成对出现
即 二元信号量可以保护临界资源
生命周期也是随内核.
查看/删除信号量的指令ipsc -s/ipcrm -m
进程间通信(1)