技术标签: Operation System linux 操作系统 c语言
为了共同完成某项任务,不同进程间需要有一种协作的方式。其中最简单的一种,就是两个进程共享一块物理内存区域。多个进程都可以在其中读写数据,这样就完成了数据从一个进程传送到另一个进程的功能。但单纯使用共享内存还不够,当多个进程并发执行,一同访问共享数据区域,就可能产生数据错误。(关于并发和竞争条件,本文不详细展开,读者可以查阅其他资料)**所以,我们还需要一种同步这些进程行为的方式。**本文我们将采用信号量实现进程同步。
让我们来考虑这种场景:有n台电脑,m台打印机。n台电脑分别在运行需要打印机输出的程序,而m台打印机都可以胜任。**它们共享一个长度为l的缓冲区,**缓冲区可以循环使用。
这就是一个实例化的生产者-消费者问题。产生数据的电脑是生产者,而将数据打印出来消耗的打印机是消费者。在下面的例子中,我们将通过共享内存和信号量实现这样一个问题。
共享内存(Shared Memory),是进程通信(IPC, Interprocess Communication)中最简单的一种方式。它允许多个进程访问同一块内存空间。共享内存的操作API如下:
原型如下:
/* Get shared memory segment. */
extern int shmget (key_t __key, size_t __size, int __shmflg) __THROW;
该函数将会创建一块共享内存,并将其标识符返回(失败返回-1)
Parameters:
key: IPC通信键值,功能类似于组ID,用于标识一组相互通信的进程。详见第4节ftok()
size: 共享内存大小,同malloc
shmflg: 共享内存标志位,包含两个部分:
IPC控制指令,用于控制shmget()函数行为,位于bits/ipc.h下,不可直接引用,需引用sys/ipc.h
/* Mode bits for `msgget', `semget', and `shmget'. */
#define IPC_CREAT 01000 /* Create key if key does not exist. */
#define IPC_EXCL 02000 /* Fail if key exists. */
#define IPC_NOWAIT 04000 /* Return error on wait. */
IPC_CREAT,如果不存在共享内存块对应的Key,就创建一个。内存权限位:同文件的权限掩码,一般以3位八进制表示,从高到低分别为rwx,我们一般使用的掩码为0666,其中0为八进制前缀。
常规用法:
int shmid = shmget(key, size, IPC_CREAT | 0666);
注意:一个IPC_Key只能创建一个共享内存段,当创建完成后,再使用这个Key创建,会直接返回Key对应的段,不会新创建(与size无关)
刚被创建完,或是刚获取到的共享内存标识符不能被直接使用,需要使用shmat(shared memory attach)映射到进程自己的内存空间中。
/* Attach shared memory segment. */
extern void *shmat (int __shmid, const void *__shmaddr, int __shmflg) __THROW;
shmid: 通过shmget获取的shmid
shmaddr: 如果指定,则将内存映射到指定位置;否则,系统将自动分配合适地址
shmflg:映射共享内存的标志参数,见下,一般不特殊指定,填0
/* Flags for `shmat'. */
#define SHM_RDONLY 010000 /* attach read-only else read-write */
#define SHM_RND 020000 /* round attach address to SHMLBA */
#define SHM_REMAP 040000 /* take-over region on attach */
#define SHM_EXEC 0100000 /* execution access */
函数返回映射的目标地址指针,失败返回-1
/* Detach shared memory segment. */
extern int shmdt (const void *__shmaddr) __THROW;
/* Shared memory control operation. */
extern int shmctl (int __shmid, int __cmd, struct shmid_ds *__buf) __THROW;
shmid: 获取的shmidcmd: 对该共享内存段执行的命令
IPC_STAT:将共享内存段信息复制到bufIPC_SET: 将buf设置的共享内存段信息设置到该段IPC_RMID:将该段标记为删除,最常用,详见注意事项IPC_LOCK:锁定该段,该段将不会被交换出内存,必须由root执行buf:被部分命令使用,用于存放内存段控制信息,IPC_RMID不需要,设置为NULL即可注意:删除共享内存段不会直接删除该段,而是将其标记为SHM_DEST (Destroy on last detach)。当没有任何一个进程映射该内存段,它会自动删除。
信号量的基本知识,本文不做展开,有兴趣的读者请自行查阅资料。
目前Linux有两套信号信号量方案,一套是基于POSIX兼容层标准的信号量,位于semahphore.h中,另一类则与上面的共享内存相同,采用SystemV的方案。
所以,我们与上面的Shared Memory保持一致,采用SystemV的方案。
信号量和共享内存同属SystemV IPC设施,接口基本一样,具有较好的通用性
/* Get semaphore. */
extern int semget (key_t __key, int __nsems, int __semflg) __THROW;
key: IPC_key, 也可以通过ftok生成,可以和shared memory重复nsems: 信号数。semget()函数初始化一个信号量数组,里面可以有多个信号量,方便使用semflg:类似于共享内存的flag,参数值同共享内存函数返回sim_id,即信号量组标识符。
常用语句:
int sem_id = semget(key, num, IPC_CREAT | 0666);
此函数可以对信号量进行管理,包括删除,初始化等。
/* Semaphore control operation. */
extern int semctl (int __semid, int __semnum, int __cmd, ...) __THROW;
semid: 信号量组标识符,通过semget()获得sumnum: 信号量在数组中的索引号,只有一个填0cmd: 信号量命令(部分主要命令)
GETVAL :返回值为对应信号量的值SETVAL:通过semun共用体设置信号的值,semun位于第四个参数IPC_RMID: 删除信号量组注意:
部分命令(SETVAL等)需要加第四个参数,类型为共用体semun
union semun
{
int val; // <= value for SETVAL
struct semid_ds *buf; // <= buffer for IPC_STAT & IPC_SET
unsigned short int *array; // <= array for GETALL & SETALL
struct seminfo *__buf; // <= buffer for IPC_INFO
};
SETVUL,将val设置为对应值即可信号量初始化示例:
int initSemaphores(void)
{
union semun sem_union;
int sem_id = getSemaphores();
// initialize semaphores
int init_val[] = {1, 0, BUFFER_SLOT_NUM, CONSUMER_NUM + PRODUCER_NUM};
for (int i = 0; i < SEM_COUNT; i++)
{
sem_union.val = init_val[i];
if (semctl(sem_id, i, SETVAL, sem_union) == -1)
perror("semaphore init");
}
return sem_id;
}
此函数对信号量进行常规操作:
/* Operate on semaphore. */
extern int semop (int __semid, struct sembuf *__sops, size_t __nsops) __THROW;
semid: 信号量组标识符,通过semget()获得
sops: 对信号量进行的操作,类型为struct sembuf
/* Structure used for argument to `semop' to describe operations. */
struct sembuf
{
unsigned short int sem_num; /* semaphore number */
short int sem_op; /* semaphore operation */
short int sem_flg; /* operation flag */
};
sem_num:操作的信号量索引sem_op:对信号量进行的操作
+1: 信号量+1,即V操作-1: 信号量-1,即P操作0: wait-for-zero操作:进程挂起等待,直到信号量为0,所有对该信号量进行此操作的线程全部被唤醒sem_flg:执行操作的设置,一般为0
SEM_UNDO: 在进程退出时,将对信号量的操作还原,即使信号量被锁定也不会阻塞nsops:对信号量操作的个数,一般为1
信号量操作示例:
enum Operation {
op_P = -1,
op_w4z,
op_V
};
void sem_PV(int sem_id, int semnum, int op)
{
struct sembuf mybuf;
mybuf.sem_flg = 0;
mybuf.sem_num = semnum;
mybuf.sem_op = op;
if (semop(sem_id, &mybuf, 1) == -1)
perror("semop");
}
IPC_Key,类似于组ID,更通俗一点,当我们持有同一个组ID时,相当于我们是一组内的,可以互相通信,这里的互相通信,就是访问同一块共享内存。那么,如何得到这个ID?C标准库为我们提供了这样一个函数ftok()。利用一个存在的目录名和一个自定义ID,生成一个特殊的IPC_Key
/* Generates key for System V style IPC. */
extern key_t ftok (const char *__pathname, int __proj_id) __THROW;
pathname:目录名称,注意必须存在,否则返回错误proj_id:一个自定义的序号,所有需要共享此内存块的都使用它 原理上,它调用stat系统调用,获取pathname对应的文件inode,拼接上proj_id后,生成整型的IPC_Key。glibc实现代码如下:
ftok (const char *pathname, int proj_id)
{
struct stat64 st;
key_t key;
if (__xstat64 (_STAT_VER, pathname, &st) < 0)
return (key_t) -1;
key = ((st.st_ino & 0xffff) | ((st.st_dev & 0xff) << 16)
| ((proj_id & 0xff) << 24));
return key;
}
但是,这种方法仍可能产生Key碰撞,别的进程正在使用的ID与当前生成ID相同,导致访问同一块内存。如果在父子进程中,还可以使用一个特殊的key——IPC_PRIVATE。
这是一个特殊的IPC_Key,完全无关的进程不能通过这个Key访问同一块共享内存。它是通过操作返回的shm对象标识符来查找的。父进程在创建子进程前获得shm对象标识符,父子进程通过相同的内存空间映射共享这个标识符,进而进行链接。IPC_PRIVATE相当于创建了一个匿名的共享内存块,只能通过标识符来调用。
此函数用于打印错误信息,使用时需要stdio.h, errno.h。当函数发生错误返回时,perror根据错误号输出错误内容,方便排错。
extern void perror (const char *__s);
一般用法:
perror("Your Description");
Your Description: Error Description.semread1.c semwrite1.c sharedMemoryread1.c sharedMemorywrite1.c 这两种通信方式比较前一博客(3)中,使用的较多,共享内存也可以参考一下ftok函数。...
信号量 1、 临界资源 同一时刻, 只能被一个进程访问的资源。 2、 临界区 访问临界资源代码区域 3、 原子操作 任何情况下都不能被打断的操作 4、 内核对象 用于对进程间通讯时, 多进程能够访问同一资源的记录。 信号量的作用: 进程间同步控制 信号量相当于记录资源能同时被多少个进程访问。 信号量的操作: 创建或获取: 如果是创建, 必须初始化。 如果获取, 则不能初始化。 减一操作: P 操作...
共享内存 基本概念 系统内核分配的一块存储区,该内存被映射到多个进程的各自的进程地址空间,多个进程都可以对共享内存中数据进行跟新。 1.编程模型: 首先用shmget创建一个共享内存,再调用shmat将共享内存映射至调用进程的地址空间。映射完成后通过返回的共享内存的读写指针进行读写,最后调用shmdt关闭共享内存的映射。 2.映射: 共享内存创建后,用户程序运行时,通过调用IPC接口函数将该存储区...
原创:https://blog.csdn.net/ndzjx/article/details/89018951 进程间通信的机制,三种: 1:信号量(不同于线程POSIX信号量,这里是进程间) 2:共享内存 3:消息队列 信号量:二进制信号量/通用信号量 #include <sys/sem.h> int semctl(int sem_id, int sem_num, in...
首先介绍一个概念IPC IPC(Inter-Process Communication)机制即进程间通信机制,我们最为熟悉的IPC机制有三种,即信号量、共享内存和消息队列。今天要介绍的共享内存机制就是IPC三大机制之一。 一.共享内存先知 共享内存是在两个正在运行的进程之间传递数据的一种非常有效的方法,它允许两个不想管的进程访问同意逻辑内存,虽然X/Open标准并没有对它做出要求,但是大多数共享内...
关于对消息队列,共享内存,信号量的具体介绍 https://blog.csdn.net/qq_46777053/article/details/108804717 mySemSercer.c mySemClient.c...
文章目录 一.测试代码 二.部分代码解释 1.在哪里加锁,解锁 2.在读写前加 sleep() 三.测试 前面简单得学习了共享内存和信号量,其中信号量的一个重要作用就是对共享资源的保护,保证共享资源在一个时刻只有一个进程独享。那么现在,就来测试一下。 一.测试代码 二.部分代码解释 1.在哪里加锁,解锁 加锁保证共享资源在一个时刻只有一个进程独享,这...
什么是同步与互斥 同步与互斥是进程间的制约关系, 同步: 是为了保证临界资源的时序的可控性,安全性。是进程间由于相互合作引起的直接制约关系。 互斥: 是为了保证对临界资源同一时间的唯一访问性。是进程间由于共享资源引起的间接制约关系。 多个进程当需要操作同一资源的时候就需要通过同步和互斥机制来实现对临界资源的安全访问。 什么是临界资源 临界资源就是:一次只允许一个进程访问的资源。 什么是信号量 从本...