0. 前言
进程是一个独立的资源管理单元,不同进程间的资源是独立的,不能在一个进程中访问另一个进程的用户空间和内存空间。但是,进程不是孤立的,不同进程之间需要信息的交互和状态的传递,因此需要进程间数据的传递、同步和异步的机制。
当然,这些机制不能由哪一个进程进行直接管理,只能由操作系统来完成其管理和维护,Linux提供了大量的进程间通信机制,包括同一个主机下的不同进程和网络主机间的进程通信,如下图所示:
- 同主机间的信息交互:
无名管道:
特点:多用于亲缘关系进程间通信,方向为单向;为阻塞读写;通信进程双方退出后自动消失
问题:多进程用同一管道通信容易造成交叉读写的问题
有名管道:
FIFO(First In First Out),方向为单向(双向需两个FIFO),以磁盘文件的方式存在;通信双方一方不存在则阻塞
消息队列:
可用于同主机任意多进程的通信,但其可存放的数据有限,应用于少量的数据传递
共享内存:
可实现同主机任意进程间大量数据的通信,但多进程对共享内存的访问存在着竞争
- 同主机进程间同步机制:信号量(Semaphore)
- 同主机进程间异步机制:信号(Signal)
- 网络主机间数据交互:Socket(套接字)
1. 信号量的原理
所谓信号量,主要用来实现进程间同步,避免并发访问共享资源;同时,信号量也可以标记资源的个数。
1). 信号量集合 - semid_ds
/* come from /usr/include/linux/sem.h */
struct semid_ds
{
struct ipc_perm sem_perm; /* permissions .. see ipc.h */
__kernel_time_t sem_otime; /* last semop time */
__kernel_time_t sem_ctime; /* last change time */
struct sem *sem_base; /* ptr to first semaphore in array */
struct sem_queue *sem_pending; /* pending operations to be processed */
struct sem_queue **sem_pending_last; /* last pending operation */
struct sem_undo *undo; /* undo requests on this array */
unsigned short sem_nsems; /* no. of semaphores in array */
};
struct sem
{
int semval; /*信号量的值*/
int sempid; /*最近一次操作的进程PID*/
}
2). 信号量系统信息 - struct seminfo
/* come from /usr/include/linux/sem.h */
struct seminfo
{
int semmap; /* Number of entries in semaphore map; unused within kernel */
int semmni; /* Maximum number of semaphore sets */
int semmns; /* Maximum number of semaphores in all semaphore sets */
int semmnu; /* System-wide maximum number of undo structures; unused within kernel */
int semmsl; /* Maximum number of semaphores in a set */
int semopm; /* Maximum number of operations for semop(2) */
int semume; /* Maximum number of undo entries per process; unused within kernel */
int semusz; /* Size of struct sem_undo */
int semvmx; /* Maximum semaphore value */
int semaem; /* Max. value that can be recorded for semaphore adjustment (SEM_UNDO) */
};
3). 信号量设置的联合体 - union semun
针对信号量的不同操作,
semctl
的第四个参数有所不同,为了方便起见,一般我们使用一个联合体对所有的可能性进行封装(自行封装,系统无此联合体)
union semun
{
int value;
struct semid_ds *buf;
unsigned short *array;
};
4). 函数 semop
参数结构体 - sembuf
semop
可通过该结构体实现信号量的 P(get)、V(release) 操作,具体看sem_op的值为正(v)还是负(P)
/* come from /usr/include/linux/sem.h */
/* semop system calls takes an array of these. */
struct sembuf {
unsigned short sem_num; /* semaphore index in array */
short sem_op; /* semaphore operation */ 信号量操作:正数,增加信号量的值;负数,减少信号量的值
short sem_flg; /* operation flags */ 包括:1.IPC_NOWAIT: 阻塞立刻返回;2.SEM_UNDO: 进程退出后,该进程对sem的操作将会撤销
};
2. 信号量的操作
1). 创建信号量集合 - semget
作用:
创建一个信号量的集合
- 头文件:
#include<sys/sem.h>
- 函数原型:
int semget(key_t key, int nsems, int semflg)
- 参数:
- key: 有函数
产生的返回值
ftok
- nsems: 创建的信号量的个数,以数组的形式存储
- semflg: 信号量集合的权限,最终值为 perm & ~umask
Macro No. Description IPC_CREAT 00001000 若key不存在,这创建 IPC_EXCL 00002000 若key存在,返回失败 IPC_NOWAIT 00004000 若需要等待,直接返回错误
返回值:
成功: 返回sem id
失败: -1
2). 控制信号量(集合) - semctl
- 对信号量集合或者对信号量集合中的某个或者某几个信号进程操作
#include<sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...)
- semid : 由
函数返回的semaphore id 号
semget
- semnum : 集合中,信号量处于信号量集合数组的下标
- cmd: 要执行的操作(以宏的形式出现)
Return Argument 4 IPC_RMID 删除 以下皆针对整个信号量集合,/usr/include/linux/ipc.h IPC_SET 1 设置ipc_perm参数 struct semid_ds IPC_STAT 2 获取ipc_perm参数 IPC_INFO 3 获取系统信息 struct seminfo GETPID 11 获取信号量拥有者的PID 以下皆针对某个信号量 /usr/include/linux/sem.h GETVAL 12 获取信号量的值,函数返回信号的值 当前信号量的值:进程数; 失败:-1 GETALL 13 获取所有信号量的值 成功:0; 失败:-1 数组地址 GETNCNT 14 获取等待信号量递增的进程数 成功:进程数; 失败:-1 GETZCNT 15 获取等待信号量值递减的进程数 int value (信号量的值) SETVAL 16 设置信号量的值,设置的值在第四个参数中 SETALL 17 设置所有信号量的值
成功: 0
失败:-1
3). 信号量操作 - semop
- 操作信号量的集合
#include <sys/sem.h>
int semop(int semid, struct sembuf *sops, unsigned nsops)
- semid: 信号量集合ID(由
返回)
semget
- sops: struct sembuf结构体变量,见上面
- nsops: 进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
- 成功: 0
3. 示例代码
生产者/消费者问题,经典PV问题,具体过程如下图所示:
本例代码分为两个文件:
- customer.c :
- producer.c :
- producer.c
/*
* Filename: producer.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <errno.h>
#include <signal.h>
int sem_id;
int init(void)
{
key_t key;
unsigned short int sem_array[2]; //for semaphore set, [0] for produce, [1] for space
union semun //used for function semop()
{
int val;
struct semid_ds *buf;
unsigned short *array;
}arg;
key = ftok(".", 0xFF);
if(-1 == key)
{
perror("ftok");
return -1;
}
sem_id = semget(key, 2, IPC_CREAT|0644);
if(-1 == sem_id)
{
perror("semget");
return -1;
}
sem_array[0] = 0;
sem_array[1] = 100;
arg.array = sem_array;
if(-1 == (semctl(sem_id, 0, SETALL, arg)))
{
perror("semctl");
return -1;
}
printf("produce init is %d\n", semctl(sem_id, 0, GETVAL));
printf("space init is %d\n", semctl(sem_id, 1, GETVAL));
return 0;
}
void del()
{
semctl(sem_id, 0, IPC_RMID);
}
int main()
{
struct sembuf sops[2]; //for function semop()
sops[0].sem_num = 0;
sops[0].sem_op = 1; //increase 1
sops[0].sem_flg = 0;
sops[1].sem_num = 1;
sops[1].sem_op = -1; //decrease 1
sops[1].sem_flg = 0;
signal(SIGALRM, NULL);
if(-1 == init())
{
fprintf(stderr,"init error!\n");
exit(EXIT_FAILURE);
}
printf("This is producer:\n");
while(1)
{
alarm(0); //cancel the previous alarm
alarm(10); //set the 10s , if timeout, the process
printf("Before produce:\n");
printf("\tThe produce is %d\n", semctl(sem_id, 0, GETVAL));
printf("\tThe space is %d\n", semctl(sem_id, 1, GETVAL));
printf("\n\nNow, producing...\n\n");
semop(sem_id, &sops[0], 1);
semop(sem_id, &sops[1], 1);
printf("After produce:\n");
printf("\tThe produce is %d\n", semctl(sem_id, 0, GETVAL));
printf("\tThe space is %d\n", semctl(sem_id, 1, GETVAL));
sleep(2);
}
del();
return 0;
}
- customer.c
/*
* Filename: producer.c
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <signal.h>
int sem_id;
int init(void)
{
key_t key;
key = ftok(".", 0xFF);
sem_id = semget(key, 2, IPC_CREAT|0644);
if(-1 == sem_id)
{
perror("semget");
return -1;
}
return 0;
}
void del()
{
semctl(sem_id, 0, IPC_RMID);
}
int main()
{
struct sembuf sops[2]; //for function semop()
sops[0].sem_num = 0;
sops[0].sem_op = -1; //increase 1
sops[0].sem_flg = 0;
sops[1].sem_num = 1;
sops[1].sem_op = 1; //decrease 1
sops[1].sem_flg = 0;
signal(SIGALRM, NULL);
if(-1 == init())
{
fprintf(stderr,"init error!\n");
exit(EXIT_FAILURE);
}
printf("This is customer:\n");
while(1)
{
alarm(0); //cancel the previous alarm
alarm(10); //set the 10s , if timeout, the process
printf("Before consume:\n");
printf("\tThe produce is %d\n", semctl(sem_id, 0, GETVAL));
printf("\tThe space is %d\n", semctl(sem_id, 1, GETVAL));
printf("\n\nNow, consuming...\n\n");
semop(sem_id, sops, 2);
printf("After consume:\n");
printf("\tThe produce is %d\n", semctl(sem_id, 0, GETVAL));
printf("\tThe space is %d\n", semctl(sem_id, 1, GETVAL));
sleep(3);
}
del();
return 0;
}