l6-d16 信号灯(信号量)

一、信号灯概念

信号量代表某一类资源,其值表示系统中该资源的数量

信号量是一个受保护的变量,只能通过三种操作来访问

        初始化

        P操作(申请资源)

        V操作(释放资源)

P(S) 含义如下:

     if  (信号量的值大于0) {  

          申请资源的任务继续运行;

          信号量的值减一;

}

     else {   申请资源的任务阻塞;}

V(S) 含义如下:

    信号量的值加一;

     if (有任务在等待资源) {

          唤醒等待的任务,让其继续运行

 }

三种信号灯:

        Posix 有名信号灯

        Posix 无名信号灯 (linux只支持线程同步)

        System V 信号灯

信号灯的含义

        Posix 信号灯:计数信号灯

        System V 信号灯:一个或多个计数信号灯的集合

                                        可同时操作集合中的多个信号灯

信号灯文件位置:/dev/shm

信号灯P操作

int sem_wait(sem_t *sem);

获取资源,如果信号量为0,表示这时没有相应资源空闲,那么调用线程就将挂起,直到有空闲资源可以获取

信号灯V操作

int sem_post(sem_t *sem);

释放资源,如果没有线程阻塞在该sem上,表示没有线程等待该资源,这时该函数就对信号量的值进行增1操作,表示同类资源多增加了一个。如果至少有一个线程阻塞在该sem上,表示有线程等待资源,信号量为0,这时该函数保持信号量为0不变,并使某个阻塞在该sem上的线程从sem_wait函数中返回

注意:编译posix信号灯需要加pthread动态库。

二、有名信号灯

有名信号灯打开

sem_t *sem_open(const char *name, int oflag);

sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);

参数:

        name:name是给信号灯起的名字

        oflag:打开方式,常用O_CREAT

        mode:文件权限。常用0666

        value:信号量值。二元信号灯值为1,普通表示资源数目

有名信号灯关闭

int sem_close(sem_t *sem);

有名信号灯的删除

int sem_unlink(const char* name); 

代码演示

        生产者等待 sem_w 信号量,然后从标准输入读取一行文本并存储到共享内存中,最后释放 sem_r 信号量以通知消费者。消费者等待 sem_r 信号量,然后读取共享内存中的数据并释放 sem_w 信号量以通知生产者可以继续写入数据。这样可以确保生产者和消费者之间的同步,并避免竞争条件。

#include <fcntl.h>           
#include <sys/stat.h>       
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>

void delsemfile(int sig){    //信号处理函数,接受sig信号编号
    sem_unlink("mysem_r");    //sem_unlink删除mysem_r信号量
    exit(0);

}

int main(){

   sem_t *sem_r,*sem_w;    //指针,存储信号量
   key_t key;                //共享内存键值
   int shmid;                //共享内存标识符
   char *shmaddr;            //共享内存地址
   
   struct sigaction act;     //结构体变量,设置信号处理函数   
   act.sa_handler = delsemfile;    //初始化,指向信号处理函数delsemfile
   act.sa_flags = 0;                //初始化
   sigemptyset(&act.sa_mask);        //清空信号屏蔽集

   sigaction(SIGINT,&act,NULL);    //接收到SIGINT信号时调用act中的信号处理函数delsemfile

   key = ftok(".",100);        //生成一个键值
   if(key<0){        
       perror("ftok");
       return 0;
   }

   shmid = shmget(key,500,0666|IPC_CREAT);    //创建一个大小为 500 字节、权限为 0666 的
   if(shmid<0){                               //共享内存段,并将其标识符赋值给变量shmid
       perror("shmget");
       return 0;
   }

   shmaddr = shmat(shmid,NULL,0);        //将共享内存段附加到进程的地址空间中,
                                         //并将其地址赋值给shmaddr
   
   //创建两个信号量
   sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);    //初始值0
   sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);    //初始值1

   while(1){
        sem_wait(sem_r);        //等待sem_r信号量
        printf("%s\n",shmaddr);
        sem_post(sem_w);        //释放sem_w信号量
   }
}

#include <fcntl.h>
#include <sys/stat.h>
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>

void delsemfile(int sig){
    sem_unlink("mysem_w");
    exit(0);
}

int main(){

	sem_t *sem_r,*sem_w;
	key_t key;
	int shmid;
	char *shmaddr;

	struct sigaction act;
	act.sa_handler = delsemfile;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);

	sigaction(SIGINT,&act,NULL);

	key = ftok(".",100);
	if(key<0){
		perror("ftok");
		return 0;
	}

	shmid = shmget(key,500,0666|IPC_CREAT);
	if(shmid<0){
		perror("shmget");
		return 0;
	}

	shmaddr = shmat(shmid,NULL,0);
   
	sem_r = sem_open("mysem_r",O_CREAT|O_RDWR,0666,0);
	sem_w = sem_open("mysem_w",O_CREAT|O_RDWR,0666,1);

	while(1){
		sem_wait(sem_w);
		printf(">");
		fgets(shmaddr,500,stdin);        //从标准输入读取一行文本并存储到共享内存中
		sem_post(sem_r);
	}
}

三、无名信号灯

无名信号灯初始化

int sem_init(sem_t *sem, int shared, unsigned int value);

参数:

        sem:需要初始化的信号灯变量

        shared: shared指定为0,表示信号量只能由初始化这个信号量的进程使用,不能在进程间使用,linux 不支持进程间同步。

        Value:信号量的值

无名信号灯销毁

int sem_destroy(sem_t* sem);

代码演示

#include <fcntl.h>
#include <sys/stat.h> 
#include <semaphore.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <pthread.h>

sem_t sem_r,sem_w;        //全局变量,存储两个信号量
char *shmaddr;            //存储共享内存地址

void destroysem(int sig){     //信号处理函数,接收到SIGINT 信号时销毁两个信号量并退出程序
//    sem_unlink("mysem_w");
	sem_destroy(&sem_r);
	sem_destroy(&sem_w);
	exit(0);
}

void *readmem(void *arg){    //线程函数,在新线程中执行
	while(1){
		sem_wait(&sem_r);    //等待sem_r信号量
		printf("%s\n",shmaddr);        //打印共享内存中的内容    
		sem_post(&sem_w);        //释放 sem_w 信号量
	}
}

int main(){

	key_t key;                //存储共享内存键值
	int shmid;                //存储共享内存标识符

	struct sigaction act;            //设置信号处理函数
	act.sa_handler = destroysem;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);        //清空信号屏蔽集

	sigaction(SIGINT,&act,NULL);    //接收到 SIGINT 信号时调用 destroysem 函数。

	key = ftok(".",100);        //生成一个键值
	if(key<0){
	    perror("ftok");
	    return 0;
	}

	shmid = shmget(key,500,0666|IPC_CREAT);    //调用shmget,来创建或获取一个大小为
                                               //500 字节、权限为 0666 的共享内存段,
                                               //并将其标识符赋值给变量 shmid
	
    if(shmid<0){
	    perror("shmget");
	    return 0;
	}

	shmaddr = shmat(shmid,NULL,0);
   
	sem_init(&sem_r,0,0);        //初始化0
	sem_init(&sem_w,0,1);        //初始化1

	pthread_t tid;            //存储新线程的标识符
	pthread_create(&tid,NULL,readmem,NULL);        //创建一个新线程,并指定其运行函数为前面定        
                                                   //义的 readmem 函数。
    
	while(1){
		sem_wait(&sem_w);        //在主线程中,在循环中,程序等待sem_w信号量
		printf(">");
		fgets(shmaddr,500,stdin);        //标准输入读取文本并存储在共享内存中
		sem_post(&sem_r);                //释放
	}
}


 

四、System V 信号灯

#include<sys/ipc.h>

#include<sys/sem.h>

        int semget(key_t key, int nsems, int semflg);

功能:创建/打开信号灯

参数:key:ftok产生的key值(和信号灯关联的key值)

   nsems:信号灯集中包含的信号灯数目

   semflg:信号灯集的访问权限,通常为IPC_CREAT |0666

返回值:  成功:信号灯集ID ;

                失败:-1

#include<sys/ipc.h>

#include<sys/sem.h>

        int semctl ( int semid, int semnum, int cmd…/*union semun arg*/);

功能:信号灯集合的控制(初始化/删除)

参数:semid:信号灯集ID

   semnum: 要操作的集合中的信号灯编号

   cmd:

           GETVAL:获取信号灯的值,返回值是获得值

           SETVAL:设置信号灯的值,需要用到第四个参数:共用体

           IPC_RMID:从系统中删除信号灯集合

返回值:成功 0 ; 失败 -1

示例:要求:假设信号灯集合中包含两个信号灯;第一个初始化为2,第二个初始化为0

union semun myun;

myun.val=2;        //第一个初值为2
if (semctl(semid, 0, SETVAL, myun)<0){    //0——第一个信号灯
    perror("semctl"); 
    exit(-1);
}
myun.val=0;        //第二个初值为0
if (semctl(semid, 1, SETVAL, myun)<0){    //0——第二个信号灯
    perror("semctl"); 
    exit(-1);
}

#include<sys/ipc.h>

#include<sys/sem.h>

        int semop ( int semid, struct sembuf *sops, size_t nsops);

功能:对信号灯集合中的信号量进行P - V操作

参数:semid:信号灯集ID

   struct sembuf {

           short sem_num;         // 要操作的信号灯的编号

           short sem_op;           // 1 : 释放资源,V操作                 // -1 : 分配资源,P操作 

           short sem_flg;          // 0(阻塞),IPC_NOWAIT(立刻返回), SEM_UNDO

   };                     //对某一个信号灯的操作;如果同时对多个操作,则需要定义这种结构体数组

   nsops: 要操作的信号灯的个数 

返回值:成功 :0 ; 失败:-1

示例:

要求:父子进程通过SystemV信号灯同步对共享内存的读写

        父进程从键盘输入字符串到共享内存

        子进程删除字符串中的空格并打印

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

#define N 64
#define READ 0
#define WRITE 1
union semun{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *_buf;
};

viod init_sem(int semid,int s[],int n)    //
{
    int i;
    union semun myun;
    
    for(i=0;i<n;i++){
        myun.val = s[i];
        semctl(semid,i,SETVAL,myun);
    }
}

viod pv(int semid,int num,int op)
{
    struct sembuf buf;
    
    buf.sem_num = num;
    buf.sem_op = op;
    buf.sem_flg = 0;
    semop(semid,&buf,1);
}

int main(){
    int shmid,semid,s[]={0,1};
    pid_t = pid;
    key_t key;
    char *shmaddr;

    if((key = ftok(".",'s')) == -1)
    {
        perror("ftok");
        exit(-1);
    }

    if((shmid = shmget(key,N,IPC_CREAT|0666)) < 0)    //共享内存
    {
        perror("shmget");
        exit(-1);
    }
    
    if((semid = semget(key,2,IPC_CREAT|0666)) < 0)    
    {
        perror("semget");
        goto _error1;
    }
    
    init_sem(semid,s,2);
    
    if((shmaddr = (char *)shmat(shmid,NULL,0)) == (char *)-1)
    {
        perror("shmat");
        goto _error2;
    }

    if((pid = fork()) < 0)
    {
        perror("fork");
        goto _error2;
    }
    else if(pid == 0)
    {
        char *q,*p;
        while(1)
        {
            pv(semid,READ,-1);
            p = q = shmaddr;
            while(*q)
            {
                if(*q !=' ')
                {
                    *p++ = *q;
                }
                q++;
            }
            *p = '\0';
            printf("%s",shmaddr);
            pv(semid,WRITE,1);
}
    }
    else{
        while(1)
        {
            pv(semid,WRITE,-1);
            printf("input >");
            fgets(shmaddr,N,stdin);
            if(strcpy(shmaddr,"quit\n") == 0)
                break;
            pv(semid,READ,1);
        }
        kill(pid,SIGUSR1);
    }

_error2:
    semctl(semid,0,IPC_RMID)
    
_error1:
    shmctl(shmid,IPC_RMID,NULL);


    return 0;
}

五、作业

编程实现无名信号灯

见前文

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值