1、ipcrm:
2、共享内存:
获取共享内存状态:
shmctl:违背了单一职责原则
cmd:
IPC_STAT:获取状态;
IPC_SET:修改状态(先使用IPC_STAT,在使用IPC_SET);
IPC_RMID:删除状态;(标记将要摧毁的内存,等到所有连接到该共享内存的进程都解除连接再删除)
shmid_ds:
设置和修改属性:
#include <43func.h>
int main(){
int shmid = shmget(1000,4096,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
void *p = (void *)shmat(shmid,NULL,0);
ERROR_CHECK(p,(void *)-1,"shmat");
struct shmid_ds stat;
int ret = shmctl(shmid,IPC_STAT,&stat);
ERROR_CHECK(ret,-1,"shmctl IPC_STAT");
printf("perm = %o\n",stat.shm_perm.mode);
printf("size = %lu\n",stat.shm_segsz);
stat.shm_perm.mode = 0666;
ret = shmctl(shmid,IPC_SET,&stat);
ERROR_CHECK(ret,-1,"shmctl IPC_SET");
shmdt(p);
}
删除共享内存:标记删除法:
shmctl RMID:标记共享内存将被删除,所有进程detach,不能再attach;
虚拟内存和物理内存的转换:
硬件:X86架构 分段 分页
分段:内存按功能分区(代码段,数据段)
分页:把内存分为固定的大小;(操作系统主要用分页);
物理页号的大小:内存的大小除去12位;
物理页号(Physical Page Number)
物理页号是指物理内存中页面的标识符。在虚拟内存管理系统中,物理页号用于将虚拟地址映射到物理地址。物理页号的大小取决于系统的内存架构和页表的设计。
- 大小:物理页号的大小通常是固定的,但具体的位数(即大小)取决于系统的内存寻址能力。例如,在32位系统中,物理页号可能占用较少的位数(因为还需要为偏移量留出空间),而在64位系统中,由于地址空间更大,物理页号可能占用更多的位数。
- 用途:物理页号用于在物理内存中定位和访问特定的页面。它是页表(Page Table)中的关键组成部分,页表将虚拟页号映射到物理页号。
控制信息(Control Information)
控制信息是一个更广泛的概念,它指的是用于管理、控制和协调计算机系统内部各种资源和操作的信息。在物理内存管理的上下文中,控制信息可能包括页表项(Page Table Entry, PTE)中的元数据,如页面的权限(可读、可写、可执行)、页面的存在性(是否在物理内存中)、页面的脏位(是否被修改过)等。
- 大小:控制信息的大小不固定,它取决于系统设计和具体实现。例如,页表项中可能包含多个控制字段,每个字段的大小和用途都不同。
- 用途:控制信息对于操作系统的内存管理至关重要。它帮助操作系统确定如何访问和保护内存页面,以及何时需要进行页面置换或写入磁盘等操作。
假设这里物理页号和控制信息的大小是4byte,在32位系统中,有20位存储页号,就有2的20次方个4byte,所以一个数组的大小为4MB,这个数组用来描述物理地址的信息,叫做页表;
页表(Page Table)是操作系统中用于存储虚拟地址到物理地址映射关系的数据结构。在虚拟内存管理系统中,物理内存被划分为固定大小的块,这些块被称为物理页(Physical Pages),而虚拟内存空间也被划分为同样大小的块,称为虚拟页(Virtual Pages)。页表的作用就是记录每个虚拟页对应的物理页的地址,从而实现虚拟地址到物理地址的转换。
页表通常是由操作系统的内存管理模块维护的,它允许程序使用比物理内存更大的地址空间。当程序尝试访问某个虚拟地址时,操作系统会查看页表,找到对应的物理地址,然后访问物理内存中的数据。如果页表中没有该虚拟地址的映射,就会发生页错误(Page Fault),操作系统会尝试将该页从磁盘加载到物理内存中,并更新页表。
页表通常存储在物理内存中,但由于页表本身也可能很大(特别是当虚拟内存空间很大时),因此现代操作系统通常会采用多级页表(也称为分页表)来减少内存占用。多级页表将虚拟地址空间分成多个层次,每个层次都有自己的页表。这样,只有实际使用的虚拟地址空间部分才会在内存中占有页表项,从而节省了大量的内存空间。
除了物理页号外,页表项(Page Table Entry, PTE)通常还包含其他控制信息,如页面的存在性、可访问性(读/写/执行权限)、脏位(页面是否被修改过)、有效位(页面是否在内存中)等。这些信息对于操作系统的内存管理至关重要,它们帮助操作系统确定如何访问和保护内存页面,以及何时需要进行页面置换或写入磁盘等操作。
给每个进程分配4M内存数组, 来存虚拟地址和物理地址的映射关系,以虚拟页号作为下标,以物理页号的信息作为元素;代价(一页4K,分配页表需要1024页,每个进程要独立页表)
一个页表能覆盖的物理内存范围取决于页表项的数量和页的大小。例如,如果页的大小是4KB,且每个页表项占用4字节,并能指向一个物理页,那么一个包含1024个页表项的页表就能覆盖4MB(1024 * 4KB)的物理内存空间。
减少页表的大小:
局部性原理:
分级页表和局部性原理在内存管理中相互配合,共同提高系统的性能和效率。
- 减少访存次数:
- 分级页表通过减少每个级别页表项的数量来降低访存次数。由于局部性原理的存在,程序在一段时间内往往会集中访问其地址空间的一小部分,因此只需要维护这部分地址空间对应的页表项即可。
- 当CPU访问一个虚拟地址时,它首先会在快表(TLB,Translation Lookaside Buffer)中查找对应的页表项。快表是页表的一个高速缓存,它存储了最近被访问的页表项。如果快表中找到了匹配的页表项,则可以直接进行地址转换,无需访问内存中的页表。这进一步减少了访存次数。
- 提高映射效率:
- 分级页表通过多级查找机制提高了虚拟地址到物理地址的映射效率。虽然多级查找会增加一定的时间开销,但由于局部性原理的存在,程序在一段时间内往往会重复访问相同的页表项,因此这种时间开销是可以接受的。
- 同时,快表的存在也进一步提高了映射效率。由于快表访问速度远快于内存中的页表,因此当快表中存在匹配的页表项时,可以极大地减少地址转换的时间开销。
分级页表:
分级页表(Multilevel Paging)是操作系统中用于管理虚拟内存的一种优化措施。它通过将页表分成多个级别来减少每个级别页表项的数量,从而降低页表占用的内存空间,并提高虚拟地址到物理地址的映射效率。以下是对分级页表的详细解释:
一、基本概念
页表:是操作系统中用于将虚拟地址映射到物理地址的一种数据结构。每个进程都有一个或多个页表,用于记录其虚拟地址空间中的页面与物理内存中页面的对应关系。
分级页表:将页表分成多个级别,每个级别的页表都指向下一级页表的基地址或物理页面的地址。通过多级查找,最终找到虚拟地址对应的物理地址。
二、分级页表的优点
- 减少内存开销:每个级别包含的条目较少,这意味着需要较少的内存来存储页面表。这对于内存资源有限的系统尤为重要。
- 更快的页面表查找:由于每个级别的条目较少,执行页面表查找所需的时间较短,这可以带来更快的系统性能。
- 灵活性:分级页表在内存空间组织方面提供了更大的灵活性,可以根据需要调整页表级别和页表大小。
三、分级页表的实现
在分级页表中,每个级别的页表都包含一组条目,每个条目指向下一级页表的基地址或物理页面的地址。以Linux系统的4级页表为例:
- PGD(Page Global Directory):页全局目录,包含指向PUD的指针。
- PUD(Page Upper Directory):页上级目录,包含指向PMD的指针。
- PMD(Page Middle Directory):页中间目录,包含指向PTE的指针。
- PTE(Page Table Entry):页表项,包含物理页面的地址和控制信息。
当CPU访问一个虚拟地址时,它会通过这四级页表逐级查找,最终找到对应的物理地址。
四、分级页表的缺点
尽管分级页表具有许多优点,但它也存在一些缺点:
- 复杂性增加:需要维护多个级别的页表,各个页表间需要相互查找,增加了系统的复杂性。
- 访问速度降低:在多个级别的页表间查找会增加访问延迟,特别是在目标页表项较少的场景下,可能会认为占用内存较多。
- 碎片化问题:多级分页可能导致内存空间的碎片化,降低系统的整体性能。
X86_64的设计:对于64位的进程,其虚拟空间有256TB虚拟内存空间,内核态和用户态各128T;
页表是每个进程各有一个;
不同的进程虚拟地址和物理地址的映射关系不同;
进程切换的代价:切换上下文(寄存器的状态(pc(cs:pic)));切换页表;
如果没有写入操作,父进程和子进程共用一个页表;若发生写入,触发缺页异常,等OS分配物理页;
竞争关系例子:
切换进程拿到的是旧的值(内存--》寄存器);
互斥:
mov p[0],R-----当移入寄存器,该进程时间片用完,要切进程时,在这个时候设置状态保护,在切换回来之前另一个进程不能加,这个叫互斥;
add R,1
mov R,p[0]
dekkr算法:
#include<43func.h>
#define NUM 10000000
int main(){
int shmid = shmget(IPC_PRIVATE,4096,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int *p = (int *)shmat(shmid,NULL,0);
ERROR_CHECK(p,(void *)-1,"shmat");
p[0] = 0;
p[1] = 0;
p[2] = 0;
p[3] = 0;
if(fork() == 0){
for(int i = 0; i < NUM; ++i){
p[1] = 1;
while(p[2]){
if(p[3] != 0){
p[1] = 0;
while(p[3] != 0){
}
p[1] = 1;
}
}
++p[0];
p[3] = 1;
p[1] = 0;
}
}
else{
for(int i = 0; i < NUM; ++i){
p[2] = 1;
while(p[1]){
if(p[3] != 1){
p[2] = 0;
while(p[3] != 1){
}
p[2] = 1;
}
}
++p[0];
p[3] = 0;
p[2] = 0;
}
wait(NULL);
printf("p[0] = %d\n", p[0]);
}
shmdt(p);
shmctl(shmid,IPC_RMID,NULL);
}
单核可以实现互斥;
信号量(semaphore):需要硬件配合
信号量(Semaphore)是一种在计算机科学中常用的同步机制,主要用于解决多个进程或线程之间的并发访问问题。以下是关于信号量的详细解释:
一、定义与类型
- 定义:信号量是一种取值为整数的变量,表示可用的临界资源数目或等待资源的进程数。进程通过调用PV原语(即Wait和Signal操作,也称为P操作和V操作)来改变信号量的值,从而实现对进程间同步或互斥的控制。
- 类型:
- 整型信号量:信号量是整数,用于记录资源的数量或等待的进程数。
- 记录型信号量:除了整数值外,还包含一个进程等待队列,用于记录阻塞在该信号量上的各个进程的标识。
- 二进制信号量:只允许信号量取0或1值,常用于实现互斥锁。
二、工作原理
信号量的工作原理基于PV操作:
- P操作(Wait操作):当进程需要访问一个资源时,会执行P操作。如果信号量的值大于0,表示资源可用,进程将信号量减1并继续执行;如果信号量的值为0,表示资源已被占用,进程将被挂起,直到资源被释放。
- V操作(Signal操作):当进程释放一个资源时,会执行V操作。如果有其他进程因等待该资源而被挂起,则将其唤醒;如果没有进程等待,则将信号量的值加1。
三、主要用途
- 进程同步:通过信号量控制进程的执行顺序,确保多个进程能够按照预定的顺序进行交互,避免数据竞争和死锁。
- 临界资源的互斥访问:当多个进程或线程共享同一个临界资源时,使用信号量实现对资源的互斥访问,确保同一时间只有一个进程或线程能够访问该资源。
- 生产者-消费者问题:在生产者-消费者模型中,生产者通过增加信号量的值来表示资源的可用数量增加,消费者通过减小信号量的值来表示资源的可用数量减少。信号量用于同步生产者和消费者的操作。
- 互斥锁实现:在并发编程中,可以使用二进制信号量来实现互斥锁,确保同一时间只有一个线程能够访问共享资源。
- 线程池管理:信号量用于控制线程池中的线程数量,确保系统负载在可控范围内。
- 顺序控制:在某些情况下,需要保证多个任务按照特定的顺序执行。信号量可以用来控制任务的执行顺序。
类比信号灯;
互斥机制:system V版本
整数:描述资源的个数;(>0绿灯 <=0红灯)
计数信号量:
二元信号量:资源要么0,要么1;
p操作:测试并加锁(测试加锁不可分割,原语,分割会出现竞争问题)原子操作
检查信号量的值(>0 --sem)(<=0 等待)
v操作:解锁
++sem
临界区:用pv操作保护起来的代码片段;越小越好(小加快系统响应);
利用信号量来保护共享资源:
system V 信号量是一个信号量集合(整数数组);
semget:
semget
是 UNIX 和 Linux 系统中用于创建新的信号量集或获取已存在的信号量集的系统调用。这个函数在进程间同步或共享资源时非常有用。以下是关于semget
函数的详细解释:一、函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg);
二、参数说明
- key:这是一个关键参数,用于标识信号量集。
key_t
是一个系统数据类型,通常是整型值。这个键值可以是一个常数(除了IPC_PRIVATE
),也可以通过ftok
函数生成,用于指定信号量集的唯一标识符。如果多个进程使用相同的键值,它们可以访问同一个信号量集。特殊的键值IPC_PRIVATE
会创建一个新的信号量集,该信号量集只能被创建它的进程及其子进程使用。- nsems:指定需要创建或检查的信号量的数量。当创建一个新的信号量集时,这个参数指定了集合中信号量的数量。如果是获取一个已存在的信号量集,这个参数通常被设置为0,因为不需要改变已有信号量集的大小。
- semflg:这个参数控制信号量集的访问权限和状态。它是一系列标志的组合,可以用来设置信号量集的访问权限。这些权限使用与文件系统相同的方式(例如,
0644
表示所有者具有读写权限,而组和其他用户具有只读权限)。IPC_CREAT
标志用于在指定的键值不存在时创建一个新的信号量集;如果信号量集已存在,这个标志没有效果。IPC_EXCL
标志与IPC_CREAT
一起使用时,如果信号量集已存在,则semget
调用会失败,这用于确保调用者创建的是一个全新的信号量集。
设置初始资源值:
ipcrm -s semid:删除
semctl:参数4设置信号量的值
semctl是Linux系统中的一个系统调用,用于在信号量集上执行控制操作。其功能和在消息队列中的系统调用msgctl相似,但参数和用法有所不同。以下是关于semctl的详细解释:
一、函数原型
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, ...);
二、参数说明
- semid:信号量集的标识符,即信号表的索引。
- semnum:信号量集中的信号量编号,从0开始。
- cmd:要执行的操作命令,如获取信号量值、设置信号量值、删除信号量集等。
- ...:这是一个可变参数,根据cmd的不同,可能需要不同的参数。通常,这个参数是一个指向
semun
联合体的指针,但在某些情况下(如GETVAL),这个参数可能不需要或者被忽略。三、常用命令
- IPC_STAT:读取信号量集的数据结构
semid_ds
,并将其存储在semun
的buf
参数中。- IPC_SET:设置信号量集的数据结构
semid_ds
中的元素ipc_perm
,其值取自semun
的buf
参数。- IPC_RMID:将信号量集从内存中删除。
- GETALL:读取信号量集中的所有信号量的值,并存入
semun
的array
参数中。- GETNCNT:返回正在等待资源(信号量值增加)的进程数目。
- GETPID:返回最后一个执行semop操作的进程的PID。
- GETVAL:返回信号量集中的一个单独信号量的值。
- GETZCNT:返回正在等待完全空闲资源(信号量值变为0)的进程数目。
- SETALL:设置信号量集中的所有信号量的值,值来自
semun
的array
参数。- SETVAL:设置信号量集中的一个单独信号量的值,值来自
semun
的val
参数。四、semun联合体
semun
联合体是在<sys/sem.h>
中定义的,用于semctl系统调用的可变参数部分。其定义如下:
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) */
};
五、返回值
- 成功时,根据不同的命令返回不同的非负值。例如,GETVAL返回信号量的值,GETNCNT返回等待信号量值增加的进程数等。
- 失败时,返回-1,并设置errno以指示错误原因,如EACCES(权限不够)、EFAULT(arg指向的地址无效)、EIDRM(信号量集已经删除)等。
union:三选一;
#include<43func.h>
#define NUM 1000000
int main(){
int shmid = shmget(IPC_PRIVATE,4096,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmget");
int *p = (int *)shmat(shmid,NULL,0);
ERROR_CHECK(p,(void *)-1,"shmat");
p[0] = 0;
//创建信号量集,(key(标识信号量集),nsems(信号量数量),semflg(权限|状态)
int semid = semget(1000,1,IPC_CREAT|0600);
ERROR_CHECK(semid,-1,"semget");
//在信号量集上执行操作(信号量标识符,信号量编号(数组下标),执行的指令,联合体内容)
int ret = semctl(semid,0,SETVAL,1);//返回信号量的值
ERROR_CHECK(ret,-1,"semctl SETVAL");
printf("semval = %d\n",ret);
实现PV操作:
P:测试并加锁 sem <=0 阻塞 sem > 0 --sem
V:解锁 ++sem
阶段1:定义PV操作;
设计struct sembuf结构体;
阶段2:调用PV操作;
semop是一个在Linux系统中用于操作信号量的函数,它允许进程对信号量进行P操作(等待或请求资源)或V操作(释放资源)。信号量是一种用于解决进程间同步与互斥问题的进程间通信机制。semop函数通过改变信号量的值来控制进程对共享资源的访问。
semop函数的基本信息
- 函数原型:
int semop(int semid, struct sembuf *sops, size_t nsops);
- 定义位置:该函数定义在头文件
sys/sem.h
中。- 参数说明:
semid
:信号量集的标识符,通过semget函数获取。sops
:指向sembuf结构体数组的指针,每个sembuf结构体代表对信号量的一个操作。nsops
:指定sops数组中操作的数量。sembuf结构体
sembuf结构体用于定义对信号量的操作,其定义如下:
struct sembuf {
unsigned short sem_num; // 信号在信号集中的索引(编号)
short sem_op; // 操作类型,正数表示V操作,负数表示P操作,0表示等待信号量为0
short sem_flg; // 操作标志,如SEM_UNDO、IPC_NOWAIT等
};
sem_num
:指定要操作的信号量在信号集中的编号,编号从0开始。sem_op
:操作类型,如果其值为正数,表示向信号量中添加资源(V操作);如果其值为负数,表示请求资源(P操作);如果其值为0,则用于等待信号量的值变为0(通常与IPC_NOWAIT一起使用,以避免阻塞)。sem_flg
:操作标志,用于控制操作的行为。常用的标志包括SEM_UNDO(程序结束时自动撤销操作,避免死锁)和IPC_NOWAIT(操作不能立即满足时立即返回,不阻塞)。semop函数的操作
- P操作:当
sem_op
的值为负数时,表示进程请求资源。如果信号量的当前值小于sem_op
的绝对值,则进程将被阻塞,直到信号量的值变得足够大以满足请求。- V操作:当
sem_op
的值为正数时,表示进程释放资源。该值将被加到信号量的当前值上,从而可能唤醒正在等待该信号量的进程。semop函数的返回值
- 成功:函数成功执行时返回0。
- 失败:函数失败时返回-1,并设置errno以指示错误原因。可能的错误包括资源暂时不可用(EAGAIN)、系统调用被中断(EINTR)、数值结果超出范围(ERANGE)等。
(1)若sem_op > 0 ,资源加上sem_op;
(2) 若sem_op < 0,且当前资源+sem_op >= 0,继续运行;
(3)若sem_op < 0,且当前资源 + sem_op < 0 ,阻塞到资源变化;
信号量的使用:
#include <43func.h>
#define NUM 1000000
int main ( ){
int shmid = shmget(IPC_PRIVATE,4096,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmid");
int *p = (int *)shmat(shmid,NULL,0);
ERROR_CHECK(shmid,(void *)-1,"shmat");
int semid = semget(1000,1,0600|IPC_CREAT);//创建信号量集合
p[0] = 0;
ERROR_CHECK(semid,-1,"semget");
int ret = semctl(semid,0,SETVAL,1);//1表示资源的量与SETVAL连用;初始化信号量值
ERROR_CHECK(ret,-1,"SETVAL");
ret = semctl(semid,0,GETVAL);//返回信号量集单独信号下标为0的值;获取信号量当前值
ERROR_CHECK(ret,-1,"semctl GETVAL");
printf("semval = %d\n",ret);
struct timeval timeBeg,timeEnd;//更高精度的时间统计
struct sembuf P,V;//依赖信号量的值执行同步或互斥操作
//P 测试加锁 <=0:阻塞 > 0 : --sem
P.sem_num = 0;//下标
P.sem_op = -1;//对资源的影响
P.sem_flg = SEM_UNDO;//默认
//V 解锁 ++sem
V.sem_num = 0;//下标
V.sem_op = 1;//对资源的影响
V.sem_flg = SEM_UNDO;//默认
if (fork() == 0)
{
for (size_t i = 0; i < NUM; i++)
{
semop(semid,&P,1);//(semid,sembuf,sops数组中操作的数量)
++p[0];
semop(semid,&V,1);
}
}else{
for (size_t i = 0; i < NUM; i++)
{
semop(semid,&P,1);
++p[0];
semop(semid,&V,1);
}
wait(NULL);
printf("%d\n",p[0]);
}
shmdt(p);
return 0;
}
更高精度的时间统计:
ettimeofday
函数是一个在计算机编程中常用的函数,主要用于获取当前的精确时间(自1970年1月1日以来的时间)。这个函数在多种编程语言中都有实现,但在这里我们主要讨论它在C语言中的用法。一、函数原型
在C语言中,
gettimeofday
函数的原型如下:
c
#include <sys/time.h>
int gettimeofday(struct timeval *tv, struct timezone *tz);
tv
:指向timeval
结构体的指针,用于接收当前时间(精确到微秒)。tz
:指向timezone
结构体的指针,用于接收时区信息。如果不需要时区信息,可以传入NULL
。二、结构体定义
timeval:用于表示时间。
struct timeval {
long int tv_sec; // 秒数
long int tv_usec; // 微秒数
};
timezone:用于表示时区信息(但通常不被广泛使用)。
struct timezone {
int tz_minuteswest; // 格林威治时间往西方的时差(分钟)
int tz_dsttime; // DST(夏令时)时间的修正方式
};
三、函数用法
获取当前时间:通过调用
gettimeofday
函数,并将tv
参数指向一个timeval
结构体变量,可以获取当前的精确时间(包括秒和微秒)。计算代码执行时间:在代码执行前后分别调用
gettimeofday
函数,并记录结果,然后计算两个时间点之间的差异,可以得到代码执行的时间。
访问一次cache0.5-1纳秒;访问内存100纳秒;本次信号量600纳秒操纵一次;
#include <43func.h>
#define NUM 1000000
int main ( ){
int shmid = shmget(IPC_PRIVATE,4096,IPC_CREAT|0600);
ERROR_CHECK(shmid,-1,"shmid");
int *p = (int *)shmat(shmid,NULL,0);
ERROR_CHECK(shmid,(void *)-1,"shmat");
int semid = semget(1000,1,0600|IPC_CREAT);//创建信号量集合
p[0] = 0;
ERROR_CHECK(semid,-1,"semget");
int ret = semctl(semid,0,SETVAL,1);//1表示资源的量与SETVAL连用;初始化信号量值
ERROR_CHECK(ret,-1,"SETVAL");
ret = semctl(semid,0,GETVAL);//返回信号量集单独信号下标为0的值;获取信号量当前值
ERROR_CHECK(ret,-1,"semctl GETVAL");
printf("semval = %d\n",ret);
struct timeval timeBeg,timeEnd;//更高精度的时间统计
struct sembuf P,V;//依赖信号量的值执行同步或互斥操作
//P 测试加锁 <=0:阻塞 > 0 : --sem
P.sem_num = 0;//下标
P.sem_op = -1;//对资源的影响
P.sem_flg = SEM_UNDO;//默认
//V 解锁 ++sem
V.sem_num = 0;//下标
V.sem_op = 1;//对资源的影响
V.sem_flg = SEM_UNDO;//默认
gettimeofday(&timeBeg,NULL);
if (fork() == 0)
{
for (size_t i = 0; i < NUM; i++)
{
semop(semid,&P,1);//(semid,sembuf,sops数组中操作的数量)
++p[0];
semop(semid,&V,1);
}
}else{
for (size_t i = 0; i < NUM; i++)
{
semop(semid,&P,1);
++p[0];
semop(semid,&V,1);
}
wait(NULL);
printf("%d\n",p[0]);
}
gettimeofday(&timeEnd,NULL);
printf("total time = %ld us\n",(timeEnd.tv_sec - timeBeg.tv_sec)*1000000+timeEnd.tv_usec-timeBeg.tv_usec);
shmdt(p);//detach 虚拟内存
shmctl(shmid,IPC_RMID,NULL);//清除物理内存标识
return 0;
}
信号量的控制:semctl:
多个信号量组成的集合:
(1)二元信号量+共享内存:
(2)计数信号量:
key相同的信号量,长度必须相同;
#include <43func.h>
int main(){
//key值相等的信号量,长度必须相同;
int semid = semget(1001,2,IPC_CREAT|0600);//
ERROR_CHECK(semid,-1,"semget");
semctl(semid,0,SETVAL,10);
semctl(semid,1,SETVAL,10);
printf("egg = %d,flour= %d ",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
}
//egg 10, flour 10
//bread need egg 3 flour 2
//cake need egg 5 flour 1
#include <43func.h>
//egg 10, flour 10
//bread need egg 3 flour 2
//cake need egg 5 flour 1
int main(){
//key值相等的信号量,长度必须相同;
int semid = semget(1001,2,IPC_CREAT|0600);//
ERROR_CHECK(semid,-1,"semget");
semctl(semid,0,SETVAL,10);
semctl(semid,1,SETVAL,10);
printf("egg = %d,flour= %d ",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
struct sembuf bread[2];//bread操作需要两个信号量,每个信号量有两个操作;
bread[0].sem_num = 0;//信号量数组的下标;信号量1;bread[0]标识操作1;
bread[0].sem_op = -3;
bread[0].sem_flg =SEM_UNDO;
//bread 操作2
bread[1].sem_num = 0;
bread[1].sem_op = -2;
bread[1].sem_flg = SEM_UNDO;
struct sembuf cake[2];//cake操作
cake[0].sem_num = 1;
cake[0].sem_op = -5;
cake[0].sem_flg = SEM_UNDO;
cake[1].sem_num = 1;
cake[1].sem_op = -1;
cake[1].sem_flg = SEM_UNDO;
if (fork() == 0)
{
while (1)
{
puts("before make bread!\n");
printf("egg = %d,flour= %d ",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
semop(semid,bread,2);//仅使用信号量,未出现临界区
puts("after make bread!\n");
printf("egg = %d,flour= %d ",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
}
}else{
puts("before make cake!\n");
printf("egg = %d,flour= %d ",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
semop(semid,cake,2);//仅使用信号量,未出现临界区
puts("after make cake!\n");
printf("egg = %d,flour= %d ",semctl(semid,0,GETVAL),semctl(semid,1,GETVAL));
wait(NULL);
}
}
SETALL: