信号量

转自:

1.信号量的概念
   信号量的本质是一种数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。
   当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值来判断资源是否可用。大于0,资源可以请求,等于0,无资源可用,进程会进入睡眠状态直至资源可用。
  当进程不再使用一个信号量控制的共享资源时,信号量的值+1,对信号量的值进行的增减操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建及初始化上,不能保证操作均为原子性。
  内核为每个信号量集都设置一个shmid_ds结构,同时用一个无名结构来标识一个信号量。
  表示一个信号量的结构:
  struct{ unsigned short semval;
          pid_t          sempid;
          unsigned short semncent;
        .....
        };
2.信号量的创建
    使用函数semget创建或者获得一个信号量集ID(不是创建一个信号量,该信号量集可以包含多个信号量)
   #include
   int semget(key_t key,int nsems, int flag);
   参数key用来变换成一个标识符,每一个IPC对象与一个key相对应。函数使用flag的相应权限位对ipc_perm结构中的mode域赋值。
   nsems是一个大于等于0的值,用于指明该信号量集中可用资源数(在第一次创建一个信号量时需要指定)。当打开一个已存在的信号量集时,该参数值为0。函数执行成功,返回信号量集的标识符,失败,返回-1.
   返回值:如果成功,则返回信号量集的IPC标识符。如果失败,则返回-1:errno=EACCESS(没有权限)
   EEXIST(信号量集已经存在,无法创建)
   EIDRM(信号量集已经删除)
   ENOENT(信号量集不存在,同时没有使用IPC_CREAT)
   ENOMEM(没有足够的内存创建新的信号量集)
   ENOSPC(超出限制)
    系统调用semget()的第一个参数是关键字值(一般是由系统调用ftok()返回的)。系统内核将此值和系统中存在的其他的信号量集的关键字值进行比较。打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和 IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。
    如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。信号量集中最多的信号量的个数是在linux/sem.h中定义的:
3.信号量所表示的资源的操作
   函数semop用以操作一个信号量集,实质是通过修改sem_op指定对资源要进行的操作,semctl函数则是对信号量本身的值进行操作,可以修改信号量的值或者删除一个信号量,这是他们二者容易混淆的地方:
   int semop(int semid,struct sembuf semoparray[],size_t nops);
   semid是通过semget函数返回的一个信号量集标识符ID,nops标明了参数semoparray所指向数组中的元素个数。semoparray是一个结构数组指针。结构体struct sembuf 用来说明要执行的操作。
  struct sembuf{
    unsigned short sem_num; //对应信号量集中的某一个资源
    short sem_op;  //指明所要执行的操作
    short  sem_flg;//函数semop的行为
   }
   在sembuf结构中,sem_num是相对应的信号量集中的某一个资源,所以它的值是一个从 0--相对应的信号量集的资源总数(ipc_perm.sem_nsems)之间的整数。
 sem_op指明想要进行的操作,sem_flg说明函数semop的行为。sem_op的值是一个整数。
sem_op:
(1)> 0:释放相应的资源数,如果有两个信号量,释放信号量1,则其semval+1,对信号量这个无名结构体的操作,可以通过下面介绍的semctl函数实现。
(2)0:进程阻塞直到信号量的相应值为0,当信号量已经为0,函数立即返回。
(3)< 0:请求sem_op的绝对值的资源数。
sem_flg 参数:
         该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新。   此外,如果此操作指定SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行---它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。
         sem_flg公认的标志是 IPC_NOWAIT 和 SEM_UNDO。如果操作指定SEM_UNDO,它将会自动撤消该进程终止时。
        在标准操作程序中的操作是在数组的顺序执行、原子的,那就是,该操作要么作为一个完整的单元,要么不。如果不是所有操作都可以立即执行的系统调用的行为取决于在个人sem_flg领域的IPC_NOWAIT标志的存在。
示例1:sem.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int sem_id;
    int nsems=1;
    int flag=0666;
    struct sembuf buf;
    

    sem_id=semget(IPC_PRIVATE,nsems,flag);
    if(sem_id<0)
    {
        printf("semget fail!\n");
        exit(1);
    }
    
    printf("semaphore :%d\n",sem_id);
    buf.sem_num=0;
    buf.sem_op=1;
    buf.sem_flg=IPC_NOWAIT;
    
    if(semop(sem_id,&buf,nsems)<0)
    {
        perror("semop error");
        exit(1);
    }
    
    system("ipcs -s");
    exit(0);
}

./sem

------ Semaphore Arrays --------
key semid owner perms nsems 
0x00000000 0 root 666 1 
0x00000000 32769 root 666 1 
0x00000000 65538 root 666 1 

semaphore :65538

示例2:sem2.c
创建了包含两个信号量的一个信号量集
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int sem_id;
    int nsems=2;
    int flag=0666;
    struct sembuf buf[2];
    

    sem_id=semget(IPC_PRIVATE,nsems,flag);
    if(sem_id<0)
    {
        printf("semget fail!\n");
        exit(1);
    }
    
    printf("semaphore :%d\n",sem_id);
    buf[1].sem_num=1;
    buf[1].sem_op=1;
    buf[1].sem_flg=IPC_NOWAIT;
    
    buf[0].sem_num=0;
    buf[0].sem_op=1;
    buf[0].sem_flg=IPC_NOWAIT;
    
    if(semop(sem_id,buf,nsems)<0)
    {
        perror("semop error");
        exit(1);
    }
    
    system("ipcs -s");
    exit(0);
}

------ Semaphore Arrays --------
key semid owner perms nsems 
0x00000000 0 root 666 1 
0x00000000 32769 root 666 1 
0x00000000 65538 root 666 1 
0x00000000 98307 root 666 2 
0x00000000 131076 root 666 2 
0x00000000 163845 root 666 2 
0x00000000 196614 root 666 2 
0x00000000 229383 root 666 2 

semaphore :229383

4.信号量集的操作
信号量集的操作函数semctl
#include
int semctl(int sem_id,int semnum, int cmd[,union semun arg]);
sem_id是信号量集标识符,semnum指定集中某一个信号灯,类似于struct sembuf 结构数组的下标,用来对指定资源进行操作。cmd定义了函数要进行的操作。
cmd的值:
GETVAL  返回结构体数组中以semnum为下标的元素的成员semval值
SETVAL 使用arg.val对该信号量的semnum.sempid赋值
GETPID GETTNCNT GETZCNT GETALL SETALL ..
arg为可选参数,根据参数cmd的相关操作来选择使用
union semun{ int val;  struct semid_ds *buf;  unsigned short *array;}
示例sem_ctl.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
    int sem_id;
    int nsems=2;
    int flag=0666;
    int val=1;
    
    sem_id=semget(IPC_PRIVATE,nsems,flag);
    if(sem_id<0)
    {
        printf("semget fail!\n");
        exit(1);
    }
    
    printf("semaphore :%d\n",sem_id);
    system("ipcs -s");
    
    if((val=semctl(sem_id,1,GETVAL))<0)
    {
        printf("getval fail!\n");
    }
    else
    {
        printf("val = %d\n",val);
    }
    
    if(semctl(sem_id,0,IPC_RMID)<0)
    {
        perror("semctl!");
        exit(1);
    }
    else
    {
        printf("semaphore removed:%d!\n",sem_id);
        system("ipcs -s");
    }
    
    exit(0);
    
}


------ Semaphore Arrays --------
key semid owner perms nsems 
0x00000000 0 root 666 1 
0x00000000 32769 root 666 1 
.....
0x00000000 393228 root 666 2 
0x00000000 720909 root 666 2 


------ Semaphore Arrays --------
key semid owner perms nsems 
0x00000000 0 root 666 1 
0x00000000 32769 root 666 1 
......
0x00000000 131076 root 666 2
0x00000000 393228 root 666 2 

semaphore :720909
val = 0  //val未初始化,尽管前面定义为1,这里还是0
semaphore removed:

以上是对信号量与信号量集的基本操作,
进程间的信号量机制的测试:sem_process.c
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h> 
#include <sys/wait.h>
#include <string.h>
#include <sys/types.h>
#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>


typedef union semunion{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
} semun;

typedef semun semunion; 

int sem_init(int key)
{
    int semid;
    semunion arg;
    
    printf("key is %d\n",key);
    semid=semget(key,1,0660|IPC_CREAT);//创建一个信号量集,且只有一个信号量,控制的资源key
    if(semid<0)
    {
        perror("semget fail!\n");
        exit(1);
    }
    
    arg.val = 2;
    if(semctl(semid,0,SETVAL,arg)<0) //设置信号量的参数val
    {
        perror("semget fail!\n");
        exit(2);
    }
    
    return semid;
}


int sem_pos(int semid)
{
    struct sembuf buf;
    buf.sem_num=0;
    buf.sem_op=-1; //请求-1的绝对值个资源,
    buf.sem_flg=SEM_UNDO;
    
    if(semop(semid,&buf,1)== -1)
    {
        perror("sem_op \n");
        exit(3);
    }
    printf("sem_poss success!\n");
    
    return 0;
}

int sem_rel(int semid)
{
    struct sembuf buf;
    buf.sem_num=0; //这个就是信号量集中的信号量元素下标
    buf.sem_op=1; //释放1 个资源,
    buf.sem_flg=IPC_NOWAIT;
    
    if(semop(semid,&buf,1)== -1)
    {
        perror("sem_op \n");
        exit(3);
    }
    printf("sem_rel success!\n");
    
    return 0;
}

int sem_rmv(int semid)
{
    
    if(semctl(semid,0,IPC_RMID)<0)
    {
        perror("semctl\n");
        exit(3);
    }
    return 0;
}


int main(void)
{
   
   key_t key;    
   int pid,fd,semid,n; 
   char str[80];

   key=ftok("./test.txt",5);//获得关键字,这就是信号量实际控制的资源
   
   semid=sem_init(key);
   
   if(semid<0)
   {
       perror("semget fail!\n");
       exit(1);
   }
   
   // 读写方式,如果文件不存在则新建,打开之后指针定位在文件尾部
   fd=open("./test.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
   while((pid=fork())==-1); //创建子进程 
   if(pid==0)//child process
   { 
    sleep(1); //睡眠1s
    sem_pos(semid);
    lseek(fd,SEEK_SET,0); //定位文件
    read(fd,str,sizeof(str)); //读文件
    sem_rel(semid); //释放操作
    printf("child:read str from test file:%s\n",str);
    exit(0);
   } 
   else //parent process 
   {
    sem_pos(semid); //     P操作
    printf("parent:please enter a str for test file(strlen<80):\n");
    gets(str);        //输入
    n=strlen(str); // 长度
    lseek(fd, SEEK_SET,0);    //定位    
    write(fd,str,n);        //写
    sem_rel(semid); //释放
    wait(0);
    close(fd); //关闭文件
    sem_rmv(semid); //删除信号量
    exit(0);
    }
        return(0);
}

以下内容转自:

信号量的值与相应资源的使用情况有关,当它的值大于 0 时,表示当前可用的资源数的数量;当它的值小于 0 时,其绝对值表示等待使用该资源的进程个数。信号量的值仅能由 PV 操作来改变。

 
     在 Linux 下,PV 操作通过调用semop函数来实现。该函数定义在头文件 sys/sem.h中,原型如下:
     int  semop(int  semid,struct sembuf  *sops,size_t nsops);
     函数的参数 semid 为信号量集的标识符;参数 sops 指向进行操作的结构体数组的首地址;参数 nsops 指出将要进行操作的信号的个数。semop 函数调用成功返回 0,失败返回 -1。
     semop 的第二个参数 sops 指向的结构体数组中,每个 sembuf 结构体对应一个特定信号的操作。因此对信号量进行操作必须熟悉该数据结构,该结构定义在 linux/sem.h,如下所示:
     struct  sembuf{
         unsigned short   sem_num;      //信号在信号集中的索引,0代表第一个信号,1代表第二个信号  
         short            sem_op;      //操作类型
         short            sem_flg;    //操作标志
     };
    下面详细介绍一下 sembuf 的几个参数:
--------------------------------------------------------------------------------------------------
sem_op 参数:
                    sem_op > 0          信号加上 sem_op 的值,表示进程释放控制的资源;
 
                    sem_op = 0          如果没有设置 IPC_NOWAIT,则调用进程进入睡眠状态,直到信号                                         量的值为0;否则进程不回睡眠,直接返回 EAGAIN
 
                    sem_op < 0          信号加上 sem_op 的值。若没有设置 IPC_NOWAIT ,则调用进程阻
                                        塞,直到资源可用;否则进程直接返回EAGAIN
sem_flg 参数:
         该参数可设置为 IPC_NOWAIT 或 SEM_UNDO 两种状态。只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新。   此外,如果此操作指定SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行---它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。
 
         sem_flg公认的标志是 IPC_NOWAIT 和 SEM_UNDO。如果操作指定SEM_UNDO,它将会自动撤消该进程终止时。
        在标准操作程序中的操作是在数组的顺序执行、原子的,那就是,该操作要么作为一个完整的单元,要么不。如果不是所有操作都可以立即执行的系统调用的行为取决于在个人sem_flg领域的IPC_NOWAIT标志的存在。
 -------------------------------------------------------------------------------------------------     

        对信号量最基本的操作就是进行PV操作,而System V信号量正是通过 semop 函数和 sembuf 结构体的数据结构来进行PV操作的。
        当 sembuf 的第二个数据结构 sem_op 设置为负数时,是对它进行P操作,即减1操作;当设置为正数时,就是进行V操作,即加1操作。
      
        下面举一个对一个信号量集中的某个信号进行 PV 操作的函数实现:
 //P操作函数
        int  sem_p( int semid, int index )
        {
                  struct  sembuf  buf  = { 0, -1, IPC_NOWAIT};
                  
                  if ( index < 0 )
                  {
                                 perror ( "index of array cannot equals a minus value!\n" );
                                 return  -1;
                  }
                  buf.sem_num = index;
                  if ( semop ( semid, &buf, 1) == -1)
                  {
                                perroe ( " a wrong operation to semaphore occurred!\n" );
                                return  -1;
                  }
                  return  0;
        }
 
        //V操作函数
        int  sem_p( int semid, int index )
        {
                  struct  sembuf  buf  = { 0, 1, IPC_NOWAIT};
                  
                  if ( index < 0 )
                  {
                                 perror ( "index of array cannot equals a minus value!\n" );
                                 return  -1;
                  }
                  buf.sem_num = index;
                  if ( semop ( semid, &buf, 1) == -1)
                  {
                                perroe ( " a wrong operation to semaphore occurred!\n" );
                                return  -1;
                  }
                  return  0;
        }
========================================================================
 
T&T的贝尔实验室,对Unix早期的进程间通信进行了改进和扩充,形成了"system V IPC",其通信进程主要局限在单个计算机内。IPC对象指的是共享内存(share memory)、消息队列(message queue)和信号灯集(semaphore)。

信号灯(semaphore),也叫信号量。它是不同进程间或一个给定进程内部不同线程间同步的机制。System V的信号灯是一个或者多个信号灯的一个集合。其中的每一个都是单独的计数信号灯。System V 信号灯由内核维护。主要函数semget,semop,semctl。

本文重点介绍的是semop函数。该函数主要功能是对信号灯进行P/V操作。

P操作责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;

V操作负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。

semop函数原型如下:

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

semop操作中:sembuf结构的sem_flg成员可以为0、IPC_NOWAIT、SEM_UNDO 。为SEM_UNDO时,它将使操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的。

sembuf结构的sem_flg成员为SEM_UNDO时,它将使操作系统跟踪当前进程对这个信号量的修改情况,如果这个进程在没有释放该信号量的情况下终止,操作系统将自动释放该进程持有的信号量

问题描述:假设父子进程对一个文件进行写操作,但是这个文件同一时间只能有一个进程进行写操作。

示例程序如下:
#include <stdio.h>
        //……此处省略了头文件
        void P(int sid)
        {
            struct sembuf sem_p;
            sem_p.sem_num = 0;
            sem_p.sem_op = -1;
            sem_p.sem_flg = 0;

            if (semop(sid, &sem_p, 1) == -1)
            {
                perror("p op failed");
                exit(1);
            }
        }

        void V(int sid)
        {
            struct sembuf sem_p;
            sem_p.sem_num = 0;
            sem_p.sem_op = 1;
            //sem_p.sem_flg = SEM_UNDO;
            sem_p.sem_flg = 0;

            if (semop(sid, &sem_p, 1) == -1)
            {
                perror("v op failed");
                exit(1);
            }
        }

        int main(int argc, char * argv[ ])
        {
            pid_t pid;
            int fd;
            key_t key;
            int sid;

            if ((fd = open("semset", O_RDWR | O_CREAT, 0666)) == -1)
            {
                perror("open");
                exit( -1);
            }

            if ((key=ftok("semset", 'a')) == -1)
            {
                perror("ftok");
                return -1;
            }

            if ((sid = semget(key, 1, IPC_CREAT | 0666)) == -1)
            {
                perror("createSemset");
                exit(-1);
            }

            if( -1==semctl(sid, 0, SETVAL, 1) )
            {
                perror("SETVAL");
                exit(1);
            }

            if ((pid=fork()) == -1)
            {
                perror("fork");
                exit(-1);
            }
            else if ( 0 == pid )
            {
                while(1)
                {
                    P(sid); 
                    printf("child writing\n");
                    sleep(1);
                    printf("child finish post\n");

                    V(sid);
                }
            }
            else
            {
                while(1)
                {
                    P(sid);
                    printf("parent writing");

                    sleep(1);
                    printf("parent writing finish post\n");

                    V(sid);
                }
            }

            return 0;
        }

在该程序中,父子进程都有可能执行P操作成功,因此,两个进程中的提示语句,交替显示。若通过kill命令把其中一个进程杀死,且该进程还没有执行V操作释放资源。若使用SEM_UNDO标志,则操作系统将自动释放该进程持有的信号量,从而使得另外一个进程可以继续工作。若没有这个标志,另外进程将P操作永远阻塞。

因此,一般建议使用SEM_UNDo标志。

=================================================

 

IPC_NOWAIT:当指定的操作不能完成时,进程不等待立即返回,返回值为-1,errno置为EAGAIN。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值