linux-共享内存-shmget-shmat-shmctl-shmdt-物理内存虚拟内存转换-页表-页框-分级页表(局部性原理)-信号量(计数二元)semget-semctl-semop-time;

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)的物理内存空间。

减少页表的大小:

局部性原理:

分级页表和局部性原理在内存管理中相互配合,共同提高系统的性能和效率。

  1. 减少访存次数
    • 分级页表通过减少每个级别页表项的数量来降低访存次数。由于局部性原理的存在,程序在一段时间内往往会集中访问其地址空间的一小部分,因此只需要维护这部分地址空间对应的页表项即可。
    • 当CPU访问一个虚拟地址时,它首先会在快表(TLB,Translation Lookaside Buffer)中查找对应的页表项。快表是页表的一个高速缓存,它存储了最近被访问的页表项。如果快表中找到了匹配的页表项,则可以直接进行地址转换,无需访问内存中的页表。这进一步减少了访存次数。
  2. 提高映射效率
    • 分级页表通过多级查找机制提高了虚拟地址到物理地址的映射效率。虽然多级查找会增加一定的时间开销,但由于局部性原理的存在,程序在一段时间内往往会重复访问相同的页表项,因此这种时间开销是可以接受的。
    • 同时,快表的存在也进一步提高了映射效率。由于快表访问速度远快于内存中的页表,因此当快表中存在匹配的页表项时,可以极大地减少地址转换的时间开销。

分级页表:

分级页表(Multilevel Paging)是操作系统中用于管理虚拟内存的一种优化措施。它通过将页表分成多个级别来减少每个级别页表项的数量,从而降低页表占用的内存空间,并提高虚拟地址到物理地址的映射效率。以下是对分级页表的详细解释:

一、基本概念

页表:是操作系统中用于将虚拟地址映射到物理地址的一种数据结构。每个进程都有一个或多个页表,用于记录其虚拟地址空间中的页面与物理内存中页面的对应关系。

分级页表:将页表分成多个级别,每个级别的页表都指向下一级页表的基地址或物理页面的地址。通过多级查找,最终找到虚拟地址对应的物理地址。

二、分级页表的优点

  1. 减少内存开销:每个级别包含的条目较少,这意味着需要较少的内存来存储页面表。这对于内存资源有限的系统尤为重要。
  2. 更快的页面表查找:由于每个级别的条目较少,执行页面表查找所需的时间较短,这可以带来更快的系统性能。
  3. 灵活性:分级页表在内存空间组织方面提供了更大的灵活性,可以根据需要调整页表级别和页表大小。

三、分级页表的实现

在分级页表中,每个级别的页表都包含一组条目,每个条目指向下一级页表的基地址或物理页面的地址。以Linux系统的4级页表为例:

  1. PGD(Page Global Directory):页全局目录,包含指向PUD的指针。
  2. PUD(Page Upper Directory):页上级目录,包含指向PMD的指针。
  3. PMD(Page Middle Directory):页中间目录,包含指向PTE的指针。
  4. PTE(Page Table Entry):页表项,包含物理页面的地址和控制信息。

当CPU访问一个虚拟地址时,它会通过这四级页表逐级查找,最终找到对应的物理地址。

四、分级页表的缺点

尽管分级页表具有许多优点,但它也存在一些缺点:

  1. 复杂性增加:需要维护多个级别的页表,各个页表间需要相互查找,增加了系统的复杂性。
  2. 访问速度降低:在多个级别的页表间查找会增加访问延迟,特别是在目标页表项较少的场景下,可能会认为占用内存较多。
  3. 碎片化问题:多级分页可能导致内存空间的碎片化,降低系统的整体性能。

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。

三、主要用途

  1. 进程同步:通过信号量控制进程的执行顺序,确保多个进程能够按照预定的顺序进行交互,避免数据竞争和死锁。
  2. 临界资源的互斥访问:当多个进程或线程共享同一个临界资源时,使用信号量实现对资源的互斥访问,确保同一时间只有一个进程或线程能够访问该资源。
  3. 生产者-消费者问题:在生产者-消费者模型中,生产者通过增加信号量的值来表示资源的可用数量增加,消费者通过减小信号量的值来表示资源的可用数量减少。信号量用于同步生产者和消费者的操作。
  4. 互斥锁实现:在并发编程中,可以使用二进制信号量来实现互斥锁,确保同一时间只有一个线程能够访问共享资源。
  5. 线程池管理:信号量用于控制线程池中的线程数量,确保系统负载在可控范围内。
  6. 顺序控制:在某些情况下,需要保证多个任务按照特定的顺序执行。信号量可以用来控制任务的执行顺序。

类比信号灯;

互斥机制: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,并将其存储在semunbuf参数中。
  • IPC_SET:设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semunbuf参数。
  • IPC_RMID:将信号量集从内存中删除。
  • GETALL:读取信号量集中的所有信号量的值,并存入semunarray参数中。
  • GETNCNT:返回正在等待资源(信号量值增加)的进程数目。
  • GETPID:返回最后一个执行semop操作的进程的PID。
  • GETVAL:返回信号量集中的一个单独信号量的值。
  • GETZCNT:返回正在等待完全空闲资源(信号量值变为0)的进程数目。
  • SETALL:设置信号量集中的所有信号量的值,值来自semunarray参数。
  • SETVAL:设置信号量集中的一个单独信号量的值,值来自semunval参数。

四、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(夏令时)时间的修正方式
    };

三、函数用法

  1. 获取当前时间:通过调用gettimeofday函数,并将tv参数指向一个timeval结构体变量,可以获取当前的精确时间(包括秒和微秒)。

  2. 计算代码执行时间:在代码执行前后分别调用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: 

  • 11
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值