• 信号量的使用


    信号量

    英文名字:semaphore

    这里的进程信号量会为下列的信号量打基础

    • Ucos系统的信号量
    • c线程的信号量
    • java进程和线程的信号量
    信号量作用

    当多个进程/线程进行共享操作时,用于资源保护,以防止出现相互干扰的情况

    信号量用于“资源的保护“

    (1)进程信号量---实现的是进程所操作资源的保护。
    (2)线程信号量---实现的是线程所操作资源的保护。
    
    • 1
    • 2
    什么是进程的资源保护

    1.多个进程同时向共享内存里面写数据时,可能会出现数据相互干扰的情况

    1. 某个进程写数据操作还没有写完时,进程的时间片就到了

    2. 然后被切换到另一个"共享内存"的写进程上运行

    3. 这个进程会接着往共享内存里面写数据,此时显然就把第一个进程写的数据给干扰了

      这就形成了数据相互干扰。

    2.当多个进程同时共享向文件里面写数据时,同样会出现和共享写“共享内存”相同的情况


    避免出现相互干扰,需要加入资源保护的措施

    保护的目的就是:

    保证每个进程在没有把数据读、写完整之前,其它进程不能进行读、写操作,以防止干扰别人

    资源保护中资源指的就是操作的数据
    保护的目的就是不要出现相互干扰,导致紊乱和错误数据的产生。
    
    • 1
    • 2

    资源保护的种类:同步或互斥

    信号量在资源保护中:即可以实现互斥又可以实现同步

    但是文件锁只能实现互斥不同实现同步

    互斥

    对于互斥操作来说,多进程共享操作时:

    1. 多个进程间不关心谁先操作、谁后操作的先后顺序问题

    2. 它们只关心一件事,那就是我在操作时别人不能操作

    ---就算当前正在操作的进程它的时间片到了,切换到了其它进程上;
    ---但是当该进程检测到上一个进程还没有操作完时,该进程在当前的时间片内会休眠;
    ---直到再次切换会上一个进程,将操作完成后再切换回来,此时才能进行操作。
    
    • 1
    • 2
    • 3

    跟上厕所时把门关起来是一样的:

    我在蹲坑时你不能蹲,你在蹲坑时我不能蹲,这就是互斥

    至于蹲坑先后顺序并没有要求。

    同步

    同步其实本身就包含了互斥,不过同步不仅仅只互斥

    同步对于谁先操作、谁后操作的先后顺序有要求

    规定A进程先写,然后是B进程写,然后是C进程写,绝对不能出现这操作顺序以外的顺序

    很像:银行办理业务,又注重互斥还注重顺序

    互斥和同步的实现本质–加锁

    信号量就是一个加锁机制,通过加锁来实现同步和互斥

    同步和互斥的目的其实就是为了实现“资源”的保护,不要让数据出现紊乱

    信号量是什么

    信号量从代码上就是:结构体,链表,数组等数据结构写的锁。

    通过数据结构可以标记这个进程的执行情况,实现加锁功能

    信号量是加锁机制,为什么会被归到了进程间通信里面

    资源保护时,某个进程的操作没有完全完成之前,别人是不能操作的

    那么进程间必须相互知道对方的操作状态,也就是一定会涉及通信过程

    信号量实现资源保护的本质

    通过通信让各个进程了解到操作状态,然后查看自己能不能操作

    信号量实现互斥的原理

    进程信号量既能实现进程的互斥,也能实现进程的同步

    父子进程同时写文件(不加信号量)
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void print_err(char *str){
        perror(str);
        exit(-1);
    }
    int main(void){
        int ret =0;
        /*fork()前打开open*/
        int fd=-1;
        //fork之前无需O_APPEND,父子进程不相互干扰
        fd=open("./share_file",O_RDWR|O_CREAT|O_TRUNC,0664);
        if(fd==-1) print_err("open fails\n");
        ret=fork();
        if(ret>0){//父进程写数据
               while(1){
                  write(fd,"aaaaaaaaa ",10);
                  write(fd,"bbbbbbbbb\n",10);
               }
        }
        else if(ret==0){//子进程写数据
              while(1){
                  write(fd,"ccccccccc ",10);
                  write(fd,"ddddddddd\n",10);
               }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    父子进程这两个相同的“文件描述符”指向的是相同的“文件表”,文件表的指针位置是同一个不会覆盖

    如果父子进程各自打开文件,各自会有自己的文件表,那么虽然通过索引得到同样的文件,但是各自文件表中的指针位置不同步,那么读写会覆盖

    不能保证互斥,因为会切换进程

    进程信号量实现互斥的原理

    什么是进程信号量
    简单理解的话:
    ----信号量其实是OS创建的一个共享变量
    --------进程在进行操作之前,会先检查这个变量的值,这变量的值就是一个标记
    --------通过这个标记就可以知道可不可以操作,以实现互斥
    
    • 1
    • 2
    • 3
    • 4

    信号量是共享变量,这个变量的值就是操作标记

    二值信号量
    • 同步和互斥时使用的都是二值信号量
    • 二值信号量的值就两个:0和1,0表示不可以操作,1表示可以操作

    比如父进程写之前将共享变量值设为0,进行加锁,两个写语句设为加锁代码段;如果没写完就切换,那么切换后的进程发现变量值为0,就阻塞等待,不进行操作;直到两个write结束了,就解锁,也就是把共享变量值设为1。如此循环

    多值信号量
    • 信号量的最大值>1,比如为3的话,信号量允许的值为0、1、2、3

    多值信号量用的不是很多

    信号量集合

    信号量其实是一个OS创建的,供相关进程共享的int变量,只不过在调用相关API创建信号量时,创建的都是一个信号量集合

    所谓集合就是可能会包含好多个信号量

    • 用于互斥时,集合中只包含一个信号量
    • 用于同步时,集合中会包含多个信号量,至于多少个,需要看情况。

    信号量的使用

    使用步骤
    1. 进程调用semget函数创建新的信号量集合,或者获取已有的信号量集合

    2. 调用semctl函数给集合中的每个信号量设置初始值

    3. 调用semop函数,对集合中的信号量进行pv操作

    4. 调用semctl删除信号量集合

    创建或获取->设初值->pv操作->删除

    PV操作

    pv操作其实说白了就是加锁、解锁操作

    • P操作(加锁):对信号量的值进行-1,如果信号量的值为0,p操作就会阻塞
    • V操作(解锁):对信号量的值进行+1,V操作不存在阻塞的问题

    通过pv操作(加锁、解锁),就能够实现互斥,以防止出现干扰

    信号量的API

    semget()
    #include 
    #include 
    #include 
    //根据key值创建新的、或者获取已有的信号量集合,并返回其标识符
    int semget(key_t key, int nsems, int semflg);
    /*
    实现互斥时:集合中只需要一个信号量
    实现同步时:集合中需要多个信号量
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • key:设置同消息队列和共享内存。

      一般都使用ftok获取key值

    • nsems:指定集合中信号量的个数

      • 用于互斥时,数量都指定为1,因为只需要一个信号量
      • 如果是同步的话就需要至多为多个
    • semflg:设置同消息队列和共享内存

      一般都设置为0664|IPC_CREAT

    成功返回信号量集合的标识符,失败返回-1

    semctl()
    #include 
    #include 
    #include 
    //根据cmd的要求对集合中的各个信号量进行控制
    int semctl(int semid, int semnum, int cmd, ...);
    //返回值:调用成功返回非-1值,失败则返回-1,errno被设置
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    ...表示它是一个变参函数,如果第四个参数用不到的话,可以省略不写

    • semid:信号量标识符

    • semnum:集合中某个信号量的编号

      • 信号量的编号为非负整数,而且是自动从0开始编号的

      通过信号量编号就能找到集合中对应信号量

    • cmd:控制选项

      • IPC_STAT:将信号量的属性信息从内核读到第四个参数所以指定的struct semid_ds缓存中

      • IPC_SET:修改属性信息,此时也会用到struct semid_ds结构体变量

      • IPC_RMID:删除信号量

        当集合中所有的信号量都被删除后,信号量集合也就被删除了

        删除操作时第四个参数用不到,比如:semctl(semid, 0, IPC_RMID);

      • SETVAL:通过第四个参数,给集合中semnu编号的信号量设置一个int初始值

    • SETVAL确属于进程信号量所独有的选项

    如果是二值信号量的话,设置初始值要么是0,要么是1

    如果信号量的目的是互斥的话,基本都是设置为1

    当设置为1后,多几个进程互斥操作时,那就是谁先运行就谁先操作

    第四个参数具体设置依赖于“cmd”

    • cmd为IPC_STAT:第四个参数应为struct semid_ds类型的缓存
    • cmd为SETVAL:第四个参数应该设置为一个int的值,用于初始化信号量

    第四个参数对应内容是变着的,为了应对这种变化就用到了一个联合体

     union semun {
    		int              val;    
    		struct semid_ds *buf;    
    		unsigned short  *array;  /* 不做要求 */
    		struct seminfo  *__buf;  /* 不做要求 */
    		};
    //这个联合体类型并没有被定义在信号量相关的系统头文件中,使用时需要自己定义这个类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    指定struct semid_ds缓存时
    union semun sem_un; //定义一个联合体变量
    struct semid_ds buff; //定义一个struct semid_ds缓存
    sem_un.buf = &buff;  //现在整个联合体的值就是buf中缩放的buff的地址	
    semctl(semid, 0, IPC_STAT, sem_un); //这里将联合体传递给semctl函数,
                                   //其实就是将buff的地址传递给了semctl函数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    定信号量的int初始值
    union semun sem_un; 
    sem_un.val = 1;  //现在整个联合体的值就是1	
    semctl(semid, 0, IPC_STAT, sem_un); 
    
    • 1
    • 2
    • 3
    删除信号量

    int semctl(int semid, int semnum, int cmd, …);

    删除信号量集合时,并不需要把所有的信号量都删除掉后才能删除
    ---------只需要指定semid和IPC_RMID就可以不把整个信号量集合删除,其中第二个参数semnum没有被用到,所以semnum的值可以随便写,不过我们一般都是把它写为0。
    所以删除整个信号量集合时,删除的写法可以统一的为:
    						semctl(semid, 0, IPC_RMID);
    
    • 1
    • 2
    • 3
    • 4
    semop()

    op是operate操作的意思

    #include 
    #include 
    #include 
    //功能:对指定的信号量进行p操作、或者是v操作
    int semop(int semid, struct sembuf *sops, size_t nsops);
    //返回值:调用成功返回0,失败则返回-1,errno被设置
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • p操作:将信号量的值-1
      • 当信号量的值为0时,p操作默认是阻塞的
    • v操作:将信号量的值+1
      • v操作不存在阻塞的问题
    对于二值信号量来说
    v操作后,值就从0变为了1
    这就表示我操作完了,其它进程运行时就可以进行p操作了
    
    • 1
    • 2
    • 3
    参数
    • semid:信号量集合的标识符
    • sops:这个参数更好理解的写法是struct sembuf sops[]
    • nsops:用于指定数组元素个数的
    1. 每一个数组成员对应一个信号量
    2. 每一个元素都是一个struct sembuf结构体变量
    
    • 1
    • 2

    内部成员的决定着:

    • 你要对集合中哪一个信号量进行操作
    • 要进行的是p操作呢,还是v操作
    //结构体成员
    	struct sembuf{
    			unsigned short sem_num;  
    			short          sem_op;
    			short          sem_flg;  
    				}
    //这个结构体不需要我们自己定义,因为在semop的头文件中已经定义了
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • sem_num:信号量编号,决定对集合中哪一个信号量进行pv操作

    • sem_op:设置为-1,表示想-1进行p操作,设置1表示想+1进行v操作

    • sem_flg

      • IPC_NOWAIT

        如果你不想阻塞的话,可以指定这个选项

      • SEM_UNDO

        如果进程在结束时没有V操作的话,OS会自动帮忙V操作,防止死锁

    以二值信号量为例:

    当进程在v操作之前就结束时,信号量的值就会一直保持为0,那么其它进程将永远无法p操作成功,会使得进程永远休眠下去,这造成就是死锁。

    互斥完整程序

    信号量封装程序
    #ifndef mm
    #define mm
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    void print_err(char* str){
        perror(str);
        exit(-1);
    }
    union semun {//联合体
    	int              val;    
    	struct semid_ds *buf;    
    };
    int create_or_get_sem(int nsems){
         int semid=0;
         int fd=open("./semaFile",O_CREAT|O_RDWR,0664);
         if(fd==-1) print_err("open fails\n");
         key_t key=0;
         key=ftok("./semaFile",'a');
         if(key==-1) print_err("ftok fails\n");
         semid=semget(key,nsems,0664|IPC_CREAT);
         if(semid==-1) print_err("semget fails\n");
         
         return semid;
    }
    //初始化信号量----传信号量的编号,信号量集合的某个具体信号编号,初值
    void init_sema(int semid,int semnum,int val){
        int ret=-1;
        union semun sem_un;
        sem_un.val=val;
        ret=semctl(semid,semnum,SETVAL,sem_un);
        if(ret==-1) print_err("semctl fails\n");
    }
    //删除信号量
    //信号量从0开始连续,传入删除的总数,单个删
    void del_sema(int semid,int nsems){
        int i=0;
        for(;i<nsems;i++)
               semctl(semid,i,IPC_RMID);
        //删除文件
       // remove("./share_file");
    }
    //加锁P操作,资源-1
    void p_sema(int semid,int sembuf[],int n){//传入要设置的信号量编号集合
        struct sembuf sem_buf[n];
        int i=0;
        for(;i<n;i++){
             sem_buf[i].sem_num=sembuf[i];
             sem_buf[i].sem_op=-1;//p操作-1
             sem_buf[i].sem_flg=SEM_UNDO;
        }
        semop(semid,sem_buf,n);
    }
    //释放锁v操作,资源(值)加一
    void v_sema(int semid,int sembuf[],int n){
        struct sembuf sem_buf[n];
        int i=0;
        for(;i<n;i++){
             sem_buf[i].sem_num=sembuf[i];
             sem_buf[i].sem_op=1;//v操作+1
             sem_buf[i].sem_flg=SEM_UNDO;
        }
        semop(semid,sem_buf,n);
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    父子进程通信程序
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "semaphore.h"
    #include 
    #define NSEMS 1
    int semID = -1;
    void signal_fun(int signo)
    { //捕获删除
        del_sema(semID, NSEMS);
        exit(-1);
    }
    int main(void)
    {
        int ret = 0;
        /*fork()前打开open*/
        int fd = -1;
        // fork之前无需O_APPEND,父子进程不相互干扰
        fd = open("./share_file", O_RDWR | O_CREAT | O_TRUNC, 0664);
        if (fd == -1)
            print_err("open fails\n");
        // fork前创建信号量
        semID = create_or_get_sem(NSEMS);
        //父进程初始化
        int i;
        for (i = 0; i < NSEMS; i++)
            init_sema(semID, i, 1); //谁先来谁使用
        ret = fork();
        if (ret > 0)
        { //父进程写数据
            //父进程捕获
            signal(SIGINT, signal_fun);
            int sem_op[1] = {0}; //定义要操作的编号集合
            while (1)
            {
                sem_op[0] = 0;
                p_sema(semID, sem_op, 1); // P加锁,资源减一
                write(fd, "aaaaaaaaa ", 10);
                write(fd, "bbbbbbbbb\n", 10);
                sem_op[0] = 0;
                v_sema(semID, sem_op, 1); // v释放锁,资源加一
            }
        }
        else if (ret == 0)
        {                        //子进程写数据
            int sem_op[1] = {0}; //定义要操作的编号集合
            while (1)
            {
                sem_op[0] = 0;
                p_sema(semID, sem_op, 1); // P加锁,资源减一
                write(fd, "ccccccccc ", 10);
                write(fd, "ddddddddd\n", 10);
                sem_op[0] = 0;
                v_sema(semID, sem_op, 1); // v释放锁,资源加一
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    程序复习

    #ifndef MY_SEMA
    #define MY_SEMA
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    //创建或得到一个信号量的semId
    //semget(key,信号量集合个数,0664|IPC_CREAT);
    int create_get_sema(int numSema){
        int fd=-1;
        key_t key=0;
        int semId=0;
        fd=open("./share_file",O_CREAT|O_RDWR,0664);
        key=ftok("./share_file",'a');
        semId=semget(key,numSema,0664|IPC_CREAT);
    }
    //联合:cmd的第四个可变参数
    union semun {
    		int  val;    
    		struct semid_ds *buf;    
    };
    //初始化信号量
    //semctl(semId,信号量集合个数,SETVAL,初值)
    void init_sema(int semId,int numSema,int val){
        union semun ss;
        ss.val=val;
        semctl(semId,numSema,SETVAL,ss);
    }
    //删除信号量
    //semctl(semId,信号量的集合个数,IPC_RMID)
    void del_sema(int semId,int n){
       int i;
       for(i=0;i
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    #include"semaphore.h"
    #include
    #include
    #define SEMA 1
    int semId=-1;
    void signal_fun(int signo){
        del_sema(semId,SEMA);
        exit(-1);
    }
    int main(){
        int fd=-1;
        fd=open("./share_file",O_CREAT|O_RDWR|O_TRUNC,0664);
        semId=create_get_sema(SEMA);
        /*信号量初始化*/
        int i;
        for(i=0;i0){//父进程写,一定别加等号
               signal(SIGINT,signal_fun);//父进程调用捕获
               int semaSET[1]={0};
           while(1){
               //p,v操作前必须定义信号量集合
               semaSET[0]=0;
               p_sema_plus(semId,semaSET,1);
               write(fd,"hello\t",6);
               write(fd,"world\n",6);
               semaSET[0]=0;//只有一个信号量,编号为0
               v_sema_minus(semId,semaSET,1);
           }
        }
        else if(ret==0){
               int semaSET[1]={0};
           while(1){
               semaSET[0]=0;
               p_sema_plus(semId,semaSET,1);
               write(fd,"learn\t",6);
               write(fd,"happy\n",6);
               semaSET[0]=0;//只有一个信号量,编号为0
               v_sema_minus(semId,semaSET,1);
           }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44

    信号量实现同步

    什么是同步

    同步本身就是互斥的,让多个进程按照固定的步调做事(多了顺序)

    让三个进程按照顺序打印:11111,22222,33333

    #ifndef MY_SEMA
    #define MY_SEMA
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    /*错误打印函数*/
    void print_err(char* str){
        perror(str);
        exit(-1);
    }
    /*创建信号量*/
    int create_get_sema(int numSema){
        int fd=-1;
        key_t key=0;
        int semId=0;
        fd=open("./share_file",O_RDWR|O_CREAT|O_TRUNC,0664);
        if(fd==-1) print_err("open fails\n");
        key=ftok("./share_file",'a');
        if(key==-1) print_err("ftok fails\n");
        semId=semget(key,numSema,IPC_CREAT|0664);
        if(semId==-1) print_err("semget fails\n");
        return semId;
    }
    union semun{
         int val;
         struct semid_ds* buf;
    };
    /*初始化信号量*/
    void init_sema(int semId,int numSema,int val){
        union semun sema;
        sema.val=val;
        semctl(semId,numSema,SETVAL,sema);
    }
    /*删除信号量*/
    void del_sema(int semId,int numSema){
        semctl(semId,0,IPC_RMID);//一下子全删
        remove("./share_file");
    }
    /*P操作,加锁,资源-1*/
    void p_sema_minus(int semId,int bufop[],int n){
        int i;
        struct sembuf sem_op[n];
        for(i=0;i<n;i++){
            sem_op[i].sem_num=bufop[i];
            sem_op[i].sem_op=-1;
            sem_op[i].sem_flg=SEM_UNDO;
        }
        semop(semId,sem_op,n);
    }
    /*V操作,释放锁,资源+1*/
    void v_sema_plus(int semId,int bufop[],int n){
        int i;
        struct sembuf sem_op[n];
        for(i=0;i<n;i++){
            sem_op[i].sem_num=bufop[i];
            sem_op[i].sem_op=1;
            sem_op[i].sem_flg=SEM_UNDO;
        }
        semop(semId,sem_op,n);
    }
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    #include"semaphore.h"
    #include 
    #include 
    #include 
    #define SEMA 3
    int semId=-1;
    void signal_fun(int signo){
        del_sema(semId,SEMA);
        exit(-1);
    }
    int main(void){
         int buf_sema[1]={0};
          int ret=0;
        /*创建信号量集合*/
        semId=create_get_sema(SEMA);
        /*初始化信号量集合*/
        int i;
        for(i=0;i0){
               //父进程再次fork()
               ret=fork();
               if(ret>0){//父进程
                   while(1){
                      buf_sema[0]=2;
                      p_sema_minus(semId,buf_sema,1);
                      printf("33333\n");
                      sleep(1);
                      buf_sema[0]=0;
                      v_sema_plus(semId,buf_sema,1);
                   }
               }
               else if(ret==0){//子进程2
                  while(1){
                    buf_sema[0]=1;
                    p_sema_minus(semId,buf_sema,1);
                    printf("22222\n");
                    sleep(1);
                    buf_sema[0]=2;
                    v_sema_plus(semId,buf_sema,1);
                    }
               }
        }
        else if(ret==0){//子进程1
                  //负责删除信号量
                  signal(SIGINT,signal_fun);
                  while(1){
                   buf_sema[0]=0;
                   p_sema_minus(semId,buf_sema,1);
                   printf("11111\n");
                   sleep(1);
                   buf_sema[0]=1;
                   v_sema_plus(semId,buf_sema,1);
                  }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nvz3DdYy-1669371657448)(/home/guojiawei/.config/Typora/typora-user-images/image-20221124181216831.png)]

    易错点
    int i;
        for(i=0;i<SEMA;i++){
            if(i==0) init_sema(semId,i,1);
            else init_sema(semId,i,0);//else千万别扔了
      }
    /*或者*/
    int i;
        for(i=0;i<SEMA;i++){
            if(i==0) {init_sema(semId,i,1);
                      break}//跳出,因为不想执行下述代码
            init_sema(semId,i,0);
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 创建信号量指定的是要用到的信号量总数,这里是3

    • 但是pv操作传入的要是具体的某一个信号量的数组集合

      int buf_sema[1]={0};
      buf_sema[0]=2;
      p_sema_minus(semId,buf_sema,1);
      buf_sema[0]=0;
      v_sema_plus(semId,buf_sema,1);
      
      • 1
      • 2
      • 3
      • 4
      • 5

      创建的三个信号量分别为:0,1,2

      每次p,v只针对一个信号量,也就是信号量数组只有一个元素

      • 数组【下标】=传入的信号量编号

    回顾共享内存实现通信

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define SHMSIZE 2046
    int shmId=-1;
    void* shmAddr=NULL;
    char buf[100]={"aaaaaaaaabbbbbbbbbbbcccccccccccccccc"};
    //创建共享内存(共享内存虚拟地址更重要,不返回shmId)
    void create_get_shmId(){
        open("./sharefile",O_RDWR|O_CREAT,0664);
        key_t key=ftok("./sharefile",'a');
        shmId=shmget(key,SHMSIZE,IPC_CREAT|0664);
    }
    //信号捕获去删除共享内存
    void signal_fun(int signo){
        //删除前必须取消映射
        shmdt(shmAddr);
        //删除共享内存
        shmctl(shmId,IPC_RMID,NULL);
        remove("./sharefile");
        exit(-1);
    }
    int main(){
        signal(SIGINT,signal_fun);
        create_get_shmId();
        //NULL:内核自动分配虚拟地址
        shmAddr=shmat(shmId,NULL,SHM_RND);
        //判错
        if(shmAddr==(void*)-1){
            perror("shmat fails\n");
            exit(-1);
        }
        //往共享内存拷贝数据
        while(1){
            memcpy(shmAddr,buf,sizeof(buf));
            sleep(2);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define SHMSIZE 2046
    int shmId=-1;
    void* shmAddr=NULL;
    //创建共享内存(共享内存虚拟地址更重要,不返回shmId)
    void create_get_shmId(){
        open("./sharefile",O_RDWR|O_CREAT,0664);
        key_t key=ftok("./sharefile",'a');
        shmId=shmget(key,SHMSIZE,IPC_CREAT|0664);
    }
    //信号捕获去删除共享内存
    void signal_fun(int signo){
        //删除前必须取消映射
        shmdt(shmAddr);
        //删除共享内存
        shmctl(shmId,IPC_RMID,NULL);
        remove("./sharefile");
        exit(-1);
    }
    int main(){
        signal(SIGINT,signal_fun);
        create_get_shmId();
        //NULL:内核自动分配虚拟地址
        shmAddr=shmat(shmId,NULL,SHM_RND);
        //判错
        if(shmAddr==(void*)-1){
            perror("shmat fails\n");
            exit(-1);
        }
        //往共享内存拷贝数据
        while(1){
            if(strlen((char *)shmAddr)!=0){//需要判断,不然打出无用空白
            printf("%s\n",(char*)shmAddr);//别忘了类型强转
            bzero(shmAddr,sizeof(shmAddr));
            }
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 共享内存中:shmat()返回的(void*)shmAddr十分重要

    不管是读还是写都需要这个地址,如果读,强转:(char*)

    • 删除需要取消映射

    也就是所有程序都需要shmdt(shmAddr)后才能删了共享内存

    即:shmctl(shmId,IPC_RMID,NULL)

    用信号量解决程序等待问题

    读进程如果优先写进程执行,共享内存没数据,会一直循环等待,从而浪费CPU

    必须:写进程先写,读进程后读,读写进程有顺序,需要用信号量同步

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "semaphore.h"
    #define SHMSIZE 2046
    int shmId=-1;
    int semaId=-1;
    void* shmAddr=NULL;
    char buf[100]={"aaaaaaaaabbbbbbbbbbbcccccccccccccccc"};
    //创建共享内存(共享内存虚拟地址更重要,不返回shmId)
    void create_get_shmId(){
        open("./sharefile",O_RDWR|O_CREAT,0664);
        key_t key=ftok("./sharefile",'a');
        shmId=shmget(key,SHMSIZE,IPC_CREAT|0664);
    }
    //信号捕获去删除共享内存
    void signal_fun(int signo){
        //删除前必须取消映射
        shmdt(shmAddr);
        //删除共享内存
        shmctl(shmId,IPC_RMID,NULL);
        remove("./sharefile");
        exit(-1);
    }
    int main(){
        signal(SIGINT,signal_fun);
        semaId=create_get_sema(2);
        //sem 0=1; sem 1=0;
        init_sema(semaId,0,1);
        init_sema(semaId,1,0);
        int semaSet[1]={0};
        create_get_shmId();
        //NULL:内核自动分配虚拟地址
        shmAddr=shmat(shmId,NULL,SHM_RND);
        //判错
        if(shmAddr==(void*)-1){
            perror("shmat fails\n");
            exit(-1);
        }
        //往共享内存拷贝数据
        while(1){
            //p 0
            semaSet[0]=0;//传入第一个信号量
            p_sema_plus(semaId,semaSet,1);
            memcpy(shmAddr,buf,sizeof(buf));
            sleep(2);
            //v 1
            semaSet[0]=1;//传入第二个信号量
            v_sema_minus(semaId,semaSet,1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "semaphore.h"
    #define SHMSIZE 2046
    int shmId=-1;
    void* shmAddr=NULL;
    int semaId=-1;
    //创建共享内存(共享内存虚拟地址更重要,不返回shmId)
    void create_get_shmId(){
        open("./sharefile",O_RDWR|O_CREAT,0664);
        key_t key=ftok("./sharefile",'a');
        shmId=shmget(key,SHMSIZE,IPC_CREAT|0664);
    }
    //信号捕获去删除共享内存
    void signal_fun(int signo){
        //删除前必须取消映射
        shmdt(shmAddr);
        //删除共享内存
        shmctl(shmId,IPC_RMID,NULL);
        del_sema(semaId);
        remove("./sharefile");
        exit(-1);
    }
    int main(){
        signal(SIGINT,signal_fun);
        semaId=create_get_sema(2);
        create_get_shmId();
        int semaSet[1]={0};
        /*初始化信号量只需要一个进程
        init_sema(semaId,0,1);
        init_sema(semaId,1,0);*/
        //NULL:内核自动分配虚拟地址
        shmAddr=shmat(shmId,NULL,SHM_RND);
        //判错
        if(shmAddr==(void*)-1){
            perror("shmat fails\n");
            exit(-1);
        }
        //往共享内存拷贝数据
        while(1){
            semaSet[0]=1;
            p_sema_plus(semaId,semaSet,1);
            //无需if,肯定有数据
            printf("%s\n",(char*)shmAddr);//别忘了类型强转
            bzero(shmAddr,sizeof(shmAddr));
            semaSet[0]=0;
            v_sema_minus(semaId,semaSet,1);
        }
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
  • 相关阅读:
    rocketmq4.9.4 ubuntu环境启动过程
    在Go中如何实现并发
    Self-Attention和CNN和RNN的区别
    linux操作系统进程控制详解
    GalaxyBase分布式集群关闭后启动
    基于springboot的家政系统 毕业设计-附源码201524
    STM32+USB3300复位枚举异常的问题
    RPA能实现哪些功能,有什么特点!
    Telegram mini app 登录小部件 | 自定义登录按钮 或 静默登录
    MongoDB快速上手
  • 原文地址:https://blog.csdn.net/weixin_47173597/article/details/128042507