操作系统之信号量


信号量
/*********************
信号量就相当于共享内存中的一个盒子,
把信号量初始化成多少就是最开始的时候往盒子里放了多少个信号!
P操作就是从盒子里拿信号,只有盒子里有信号时才能拿,
若盒子里没有信号,你就只能在盒子旁边等到什么时候盒子里有信号了,拿到信号才能走!
V操作就是往盒子里放信号,不管盒子里有没有信号,你都可以往里面放信号!

*********************/

一.【信号量是】在计算机中用来模拟信号灯的一个【整数】
当这个整数变为非0才能进行某种操作许可;
在进行操作的同时,将该整数减1,以此改变信号灯,
将减1操作称为P操作,也称获取信号;
当该操作进行完之后,将该整数加1,以此来恢复信号灯,
将加1操作称为V操作,也称释放信号;

信号量的原理可以模拟为一下代码:
int i=N;        //初始化信号量
if(i != 0)
{
    i--;    //可以进行P操作
    //i++    //也可以进行V操作
    进行某种操作.....
    i++;    //V操作
}
else    if(i == 0)
{
    i++;    //只能进行V操作
}

二.进程中使用信号量:
1.获取key,使用函数ftok()
key_t key=ftok("/home",'a');
"/home":字符串形式的文件路径,要求必须存在且可访问
'a'    :整型的项目ID,要求非0,低8位被使用,一般写一个字符

2.创建/获取信号量,使用函数semget()函数
int semid = semget(key,1,0777 | IPC_CREAT);
key:    ftok的返回值
1:        信号量的个数
0777 | IPC_CREAT:    标志,可用    IPC_CREAT   IPC_EXCL    
注:创建新的信号量时,需要指定权限,如0664 | IPC_CREAT

3.初始化信号量,使用函数semctl()函数
int res = semctl(semid,0,SETVAL,1);
if(res < 0)
{
    perror("semctl");
    exit(-1);
}
semid:semid,semget的返回值
0:信号量的下标
SETVAL:操作命令
    IPC_RMID        删除信号量,不需要第四个参数
    IPC_SETVAL        使用第4个参数的值初始化信号量
1:可变参数,是否需要由第三参数决定 

4.定义结构体指针
    struct sembuf
    {
        unsigned short sem_num;  /* 信号量所在下标 */
        short sem_op;   /* 具体操作,正数增加,负数减少*/
        short sem_flg;  /* 标志 IPC_NOWAIT and SEM_UNDO. */
    };

5.初始化结构体指针
P操作:
sb.sem_num = 0;        
sb.sem_op = -1;        //P操作将其初始化为-1
sb.sem_flg = SEM_UNDO;
V操作:
sb.sem_num = 0;
sb.sem_op = 1;            //V操作将其初始化为1
sb.sem_flg = SEM_UNDO;

6.操作信号量,使用函数semop()函数
int res = semop(semid,&sb,1);
if(res < 0)
{
    perror("semop");
    exit(-1);
}
semid:获取的信号量
&sb:结构体指针
    struct sembuf
    {
        unsigned short sem_num;  /* 信号量所在下标 */
        short sem_op;   /* 具体操作,正数增加,负数减少*/
        short sem_flg;  /* 标志 IPC_NOWAIT and SEM_UNDO. */
    };
1:结构体指针指向的结构体数组的元素个数

ps:访问共享资源

7.删除信号量,使用函数semctl()函数
int semctl(semid,0,IPC_RMID);
semid:获取的信号量
0:信号量的下标
IPC_RMID:删除信号量,不需要第四个参数代码实例:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>

struct sembuf sb;
char *s="exit";
void p(int semid)
{
    sb.sem_num = 0;        
    sb.sem_op = -1;
    sb.sem_flg = SEM_UNDO;
    int res = semop(semid,&sb,1);
    if(res < 0)
    {
        perror("sem_op  p");
        exit(-1);
    }
    return ;
}
void v(int semid)
{
    sb.sem_num = 0;
    sb.sem_op = 1;
    sb.sem_flg = SEM_UNDO;
    int res = semop(semid,&sb,1);
    if(res < 0)
    {
        perror("sem_op   v");
        exit(-1);
    }
    return ;
}
int main()
{
    //1.生成key
    key_t key=ftok("/home",'a');
    if(key < 0)
    {
        perror("ftok");
        exit(-1);    //直接结束进程
    }
    
    //2.1 创建信号量
    int semid = semget(key,1,0777 | IPC_CREAT);
    if(semid < 0)
    {
        perror("semget");
        exit(-1);
    }
    
    //2.2创建共享内存
    int shmid = shmget(key,4096,0777 | IPC_CREAT);
    if(shmid < 0)
    {
        perror("shmget");
        exit(-1);
    }
    
    //3.1 初始化信号量
    int res = semctl(semid,0,SETVAL,1);
    if(res < 0)
    {
        perror("semctl");
        exit(-1);
    }    
    
    //3.2 映射共享内存
    void *addr = (void *)shmat(shmid,NULL,0);
    //0是标志,代表可读可写
    if(addr == (void *)-1)
    {
        perror("shmat");
        exit(-1);
    }    
    
    //4.创建进程
    pid_t pid = fork();
    if(pid < 0)
    {
        perror("fork");
        exit(-1);
    }
    else if(pid == 0)
    {        //子进程中接收并打印共享内存中的数据
        while(1)
        {
            if(!strcmp(addr,s))
            {
                //8.解除子进程的映射
                shmdt(addr);                
                //9.删除共享内存
                shmctl(shmid,IPC_RMID,NULL);
                break;
            }        
            //6.打印共享内存中数据            
            p(semid);
            printf("semid buf = %s\n",(char *)addr);
            v(semid);
        }
        exit(0);
    }
    else if(pid > 0)
    {    //父进程中写数据存入共享内存
        while(1)
        {
            //5.写数据存入共享内存
            p(semid);
            char buf[1024] = {0};
            scanf("%s",buf);                //写数据存入缓存区
            memcpy(addr,buf,sizeof(buf));     //将缓存区中数据拷贝到共享内存中
            v(semid);
            printf("buf = %s\n",buf);       //打印要写入的数据
            if(!strcmp(buf,s))
            {
                break;
            }    
        }
        //7.解除父进程的映射
        shmdt(addr);
        wait(NULL);
    }    
    return 0;
}


}

三.线程中使用信号量
1.定义一个信号量
sem_t    sem;

2.初始化信号量    sem_init()函数
sem_init(&sem,0,1);
&sem:    定义的信号量的地址
第二个参数:0:表示同一进程中的线程间共享,非0:进程间共享
1:    信号量的初始值
注:sem处信号量为同一进程中线程共享的信号量,
信号的初始值为1,即P、V操作从1开始

3.获取信号量(即P操作)
sem_wait(&sem);
&sem:定义的信号量的地址

ps:访问共享资源

4.释放信号量(即V操作)
sem_post(&sem);
&sem:定义的信号量的地址

5.销毁信号量
sem_destroy(&sem);
&sem:定义的信号量的地址代码实例:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<string.h>
#include <semaphore.h>

int count = 0;                
sem_t sem;//定义信号量
//线程函数
void *thread_func(void *arg)
{
    sem_wait(&sem);        //获取信号量
    int *p = (int*)arg;    //获得线程创建时传进来的参数
    int num = *p;
    printf("child num = %d\n",num);
    int i;
    for(i = 0; i < num; i++)
    {
        count++;
    }
    sem_post(&sem);    //释放信号量
}
int main()
{
    int i;
    int num = 1000000;
    int res;
    pthread_t tid[2];        
    sem_init(&sem,0,1);    //初始化信号量    
    //创建两个线程,线程ID保存在数组tid中
    for(i = 0; i < 2; i++)
    {
        res = pthread_create(&tid[i],NULL,thread_func,(void*)&num);
        if(res != 0)
        {
            fprintf(stderr,"thread create error: %s\n",strerror(res));
            exit(-1);
        }
        printf("creat new thread id = %lu\n",tid[i]);
    }
    //等待线程结束
    for(i = 0; i < 2; i++)
    {
        pthread_join(tid[i],NULL);
        printf("thread %d exited!!\n",i);
    }
    printf("count  =  %d\n",count);
    sem_destroy(&sem);    //不再使用,销毁信号量
    return 0;
}

【用多个信号量控制访问的先后顺序】:
    sem_t sem1;
    sem_t sem2;

1.将需要优先访问的线程/进程初始化成非0;
    sem_init(&sem1,0,1);
2.将后访问的线程/进程初始化成0;
    sem_init(&sem2,0,0);
3.在优先访问的线程/进程的访问过程中对需要后访问的线程/进程
进行V操作
    sem_wait(&sem1);
    执行任务。。。。。
    任务完成
    sem_post(&sem2);
4.此时后访问的进程才可以访问,在访问过程中对需要先访问的线程/进程
进行V操作
    sem_wait(&sem2);    
    sem_post(&sem1);
【程序执行流程】:先执行sem1,碰到sem_post(&sem2);后,
sem1休眠,立即执行sem2,碰到sem_post(&sem1);后,
继续执行后面的任务,执行完后再去执行sem1中sem_post(&sem2);后面的程序

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值