精华内容
下载资源
问答
  • Linux信号量详解

    万次阅读 2018-05-02 13:29:02
    Linux信号量详解1.什么是信号量信号量是一种特殊的变量,访问具有原子性。只允许对它进行两个操作:1)等待信号量当信号量值为0时,程序等待;当信号量值大于0时,信号量减1,程序继续运行。2)发送信号量将信号量值加...

    Linux信号量详解

    1.什么是信号量
    信号量是一种特殊的变量,访问具有原子性。
    只允许对它进行两个操作:
    1)等待信号量
    当信号量值为0时,程序等待;当信号量值大于0时,信号量减1,程序继续运行。
    2)发送信号量
    将信号量值加1。

    我们使用信号量,来解决进程或线程间共享资源引发的同步问题。

    2.Linux中信号量的使用
    Linux提供了一组信号量API,声明在头文件sys/sem.h中。
    1)semget函数:新建信号量

    int semget(key_t key,int num_sems,int sem_flags);

    key:信号量键值,可以理解为信号量的唯一性标记。
    num_sems:信号量的数目,一般为1
    sem_flags:有两个值,IPC_CREATE和IPC_EXCL,
    IPC_CREATE表示若信号量已存在,返回该信号量标识符。
    IPC_EXCL表示若信号量已存在,返回错误。

    返回值:相应的信号量标识符,失败返回-1

    2)semop函数:修改信号量的值

    int semop(int sem_id,struct sembuf *sem_opa,size_t num_sem_ops);

    sem_id:信号量标识符
    sem_opa:结构如下

    复制代码
    struct sembuf{  
        short sem_num;//除非使用一组信号量,否则它为0  
        short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,  
                        //一个是+1,即V(发送信号)操作。  
        short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,  
                        //并在进程没有释放该信号量而终止时,操作系统释放信号量  
    }; 
    复制代码

    3)semctl函数:用于信号量的初始化和删除

    int semctl(int sem_id,int sem_num,int command,[union semun sem_union]);

    command:有两个值SETVAL,IPC_RMID,分别表示初始化和删除信号量。
    sem_union:可选参数,结构如下:

    union semun{  
        int val; 
        struct semid_ds *buf;  
        unsigned short *arry;  
    }; 

    一般用到的是val,表示要传给信号量的初始值。

    3.Linux信号量使用示例
    下例中,我们写了一个程序,程序中有一个char类型的字符,char message='x'
    然后同时运行这个程序的两个实例。
    第一个实例,带一个参数,将参数的第一个字符赋给message,比如为'0'
    第二个实例,使用默认message值'x'
    我们的目的是,使用信号量,循环执行这两个实例,
    我们可以看到执行结果应该是'x0x0x0x0x0x0'

    复制代码
    #include<stdio.h>
    #include<stdlib.h>
    #include<sys/sem.h>
    union semun
    {
        int val;
        struct semid_ds *buf;
        unsigned short *array;
    };
    int sem_id;
    int set_semvalue()
    {
        union semun sem_union;    
        sem_union.val = 1;
        if(semctl(sem_id,0,SETVAL,sem_union)==-1)
            return 0;
        return 1;
    }
    int semaphore_p()
    {
        struct sembuf sem_b;
        sem_b.sem_num = 0;
        sem_b.sem_op = -1;
        sem_b.sem_flg = SEM_UNDO;
        if(semop(sem_id,&sem_b,1)==-1)
        {
            fprintf(stderr,"semaphore_p failed\n");
            return 0;
        }
        return 1;
    }
    int semaphore_v()
    {
        struct sembuf sem_b;
        sem_b.sem_num = 0;
        sem_b.sem_op = 1;
        sem_b.sem_flg = SEM_UNDO;
        if(semop(sem_id,&sem_b,1)==-1)
        {
            fprintf(stderr,"semaphore_v failed\n");
            return 0;
        }
        return 1;
    }
    void del_semvalue()
    {
        //删除信号量
        union semun sem_union;
        if(semctl(sem_id,0,IPC_RMID,sem_union)==-1)
            fprintf(stderr,"Failed to delete semaphore\n");
    }
    int main(int argc,char *argv[])
    {
        char message = 'x';
        //创建信号量
         sem_id = semget((key_t)1234,1,0666|IPC_CREAT);
        if(argc>1)
        {
            //初始化信号量
            if(!set_semvalue())
            {
                fprintf(stderr,"init failed\n");
                exit(EXIT_FAILURE);
            }
            //参数的第一个字符赋给message
            message = argv[1][0];
        }
        int i=0;
        for(i=0;i<5;i++)
        {
            //等待信号量
            if(!semaphore_p())
                exit(EXIT_FAILURE);
            printf("%c",message);
            fflush(stdout);
            sleep(1);
            //发送信号量
            if(!semaphore_v())
                exit(EXIT_FAILURE);
            sleep(1);
        }
        printf("\n%d-finished\n",getpid());
        if(argc>1)
        {
            //退出前删除信号量
            del_semvalue();
        }
        exit(EXIT_SUCCESS);
    }
    复制代码

    输出结果:

    展开全文
  • linux信号量详解

    2009-06-30 20:17:06
    关于linux c 开发中的信号量的使用,有详细的说明
  • linux 内核信号量 用户态信号量 详解.pdf
  • Linux 内核中的信号量使用和用户态的信号量使用有所不同, 1、内核信号量,由内核控制路径使用。 2、用户态信号量分为两种,一种为POSIX,另一种为 SYSTEM V 内核中信号量的构成以及使用: 内核信号量的构成 内核信号...

    Linux  内核中的信号量使用和用户态的信号量使用有所不同,
    1、内核信号量,由内核控制路径使用。
    2、用户态信号量分为两种,一种为POSIX,另一种为 SYSTEM V
    内核中信号量的构成以及使用:
    内核信号量的构成
    内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而,当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源被释放时,进程才再次变为可运行。
    只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内核信号量。
    内核信号量是struct semaphore类型的对象,它在<asm/semaphore.h>中定义
    struct semaphore {
       atomic_t count;
       int sleepers;
       wait_queue_head_t wait;
      }
    count:相当于信号量的值,大于0,资源空闲;等于0,资源忙,但没有进程等待这个保护的资源;小于0,资源不可用,并至少有一个进程等待资源。
    wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。
    sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠
    内核信号量中的等待队列(删除,没有联系)
    上面已经提到了内核信号量使用了等待队列wait_queue来实现阻塞操作。当某任务由于没有某种条件没有得到满足时,它就被挂到等待队列中睡眠。当条件得到满足时,该任务就被移出等待队列,此时并不意味着该任务就被马上执行,因为它又被移进工作队列中等待CPU资源,在适当的时机被调度。 内核信号量是在内部使用等待队列的,也就是说该等待队列对用户是隐藏的,无须用户干涉
    内核信号量的相关函数
    初始化:
    void sema_init (struct semaphore *sem, int val);
    void init_MUTEX (struct semaphore *sem); //将sem的值置为1,表示资源空闲
    void init_MUTEX_LOCKED (struct semaphore *sem); //将sem的值置为0,表示资源忙
    申请内核信号量所保护的资源:
    void down(struct semaphore * sem); // 可引起睡眠
    int down_interruptible(struct semaphore * sem); // down_interruptible能被信号打断
    int down_trylock(struct semaphore * sem); // 非阻塞函数,不会睡眠。无法锁定资源则马上返回
    释放内核信号量所保护的资源:
    void up(struct semaphore * sem);
    内核信号量的使用例程
    在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。Linux内核中解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。

    [html]  view plain  copy
     print ?
    1. static ssize_t globarl_var(struct file *file, const char __user *ubuf,  
    2.     size_t count,loff_t *offp)  
    3. {  
    4. //试图获得信号量,用可被信号打断方式  
    5.         if(down_interruptible(&sema) < 0){  
    6.             return -ERESTARTSYS;  
    7.         }  
    8. //对共享资源(global_var)进行操作  
    9.         if(copy_from_user(&global_var, buf, sizeof(int))){  
    10. //失败也要进行释放信号量,要不死锁了  
    11.             up(&sema);  
    12.             return -EFAULT;  
    13.         }  
    14. //成功释放信号量     
    15.         up(&sema);  
    16.           
    17.         return sizeof(int);  
    18. }  
    用户态信号量使用:
    POSIX 信号量与SYSTEM V信号量的比
    1.对POSIX来说,信号量是个非负整数。常用于线程间同步。而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,这个结构体是为SYSTEM V IPC服务的,信号量只不过是它的一部分。常用于进程间同步。
    2.POSIX信号量的引用头文件是“<semaphore.h>”,而SYSTEM V信号量的引用头文件是“<sys/sem.h>”。
    3.从使用的角度,System V信号量是复杂的,而Posix信号量是简单。比如,POSIX信号量的创建和初始化或PV操作就很非常方便。
    POSIX信号量详解
    1.无名信号量
        无名信号量的创建就像声明一般的变量一样简单,例如:sem_t  sem_id。然后再初始化该无名信号量,之后就可以放心使用了。
        无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量,这两个条件是缺一不可的。

        常见的无名信号量相关函数:sem_destroy


        int sem_init(sem_t *sem, int  pshared , unsigned int value);
         1) pshared ==0 用于同一多线程的同步;
         2)若pshared>0 用于多个相关进程间的同步(即由fork产生的)

        int sem_getvalue(sem_t *sem, int *sval);
        取回信号量sem的当前值,把该值保存到sval中。
        若有1个或更多的线程或进程调用sem_wait阻塞在该信号量上,该函数返回两种值:
         1) 返回0
         2) 返回阻塞在该信号量上的进程或线程数目
         linux采用返回的第一种策略。


        sem_wait(或sem_trywait)相当于P操作,即申请资源。
        int sem_wait(sem_t *sem);     // 这是一个阻塞的函数    
        测试所指定信号量的值,它的操作是原子的,
        若sem>0,那么它减1并立即返回。
        若sem==0,则睡眠直到sem>0,此时立即减1,然后返回。
        int sem_trywait(sem_t *sem);   // 非阻塞的函数
        其他的行为和sem_wait一样,除了:
        若sem==0,不是睡眠,而是返回一个错误EAGAIN。 
        sem_post相当于V操作,释放资源。


        int sem_post(sem_t *sem);
        把指定的信号量sem的值加1;
        呼醒正在等待该信号量的任意线程。 
        注意:在这些函数中,只有sem_post是信号安全的函数,它是可重入函数


    (a)无名信号量在多线程间的同步
    无名信号量的常见用法是将要保护的变量放在sem_wait和sem_post中间所形成的临界区内,这样该变量就会被保护起来,例如:

    [html]  view plain  copy
     print ?
    1. #include <pthread.h>  
    2. #include <semaphore.h>  
    3. #include <sys/types.h>  
    4. #include <stdio.h>  
    5. #include <unistd.h>  
    6.   
    7. int global_var;  
    8. sem_t sem_id;  
    9.   
    10. void* thread_fun2(void *arg)  
    11. {  
    12. //偿试信号量是否大于0,如果是减1,如果不是等待  
    13.         sem_wait(&sem_id);  
    14.         printf("fun2\n");  
    15.         global_var--;  
    16.         printf("global_var = %d\n",global_var);  
    17. //信号量加1  
    18.         sem_post(&sem_id);  
    19.       
    20. }  
    21. void* thread_fun1(void *arg)  
    22. {  
    23.   
    24. //偿试信号量是否大于0,如果是减1,如果不是等待  
    25.         sem_wait(&sem_id);  
    26.         printf("fun1\n");  
    27.         global_var--;  
    28.         printf("global_var = %d\n",global_var);  
    29. //信号量加1  
    30.         sem_post(&sem_id);  
    31.       
    32. }  
    33.   
    34. int main(void)  
    35. {  
    36.     global_var = 1;  
    37.     pthread_t id1, id2;  
    38.   
    39. //初始化信号量  
    40.     sem_init(&sem_id,0,1);  
    41.   
    42.     pthread_create(&id1, NULL, thread_fun1,NULL);  
    43.     pthread_create(&id2, NULL, thread_fun2,NULL);  
    44.     pthread_join(id1,NULL);  
    45.     pthread_join(id2,NULL);  
    46.   
    47.     printf("main...\n");  
    48.   
    49.     return 0;  
    50.   
    51. }  

      上面的例程,到底哪个线程先申请到信号量资源,这是随机的。如果想要某个特定的顺序的话,可以用2个信号量来实现。例如下面的例程是线程1先执行完,然后线程2才继续执行,直至结束。

    [cpp]  view plain  copy
     print ?
    1. #include <stdio.h>  
    2. #include <pthread.h>  
    3. #include <semaphore.h>  
    4.   
    5. sem_t sem_id1,sem_id2;  
    6.   
    7. int global_var;  
    8.   
    9. void * pt1_func(void *argc)  
    10. {  
    11.     while(1){  
    12.         sem_wait(&sem_id1);  
    13.   
    14.         global_var =0;  
    15.         printf("func 1 global_var = %d\n",global_var);  
    16.         sleep(2);  
    17.   
    18.         sem_post(&sem_id2);  
    19.     }  
    20. }  
    21.   
    22. void *pt2_func(void *argc)  
    23. {  
    24.     while(1){  
    25.         sem_wait(&sem_id2);  
    26.           
    27.         global_var = 1;  
    28.         printf("func 2 global_var = %d\n",global_var);  
    29.         sleep(2);  
    30.   
    31.         sem_post(&sem_id1);  
    32.     }  
    33. }  
    34.   
    35. int main(void)  
    36. {  
    37.     pthread_t pt1;  
    38.     pthread_t pt2;  
    39.   
    40.     sem_init(&sem_id1,0,1);  
    41.     sem_init(&sem_id2,0,0);  
    42.   
    43.     pthread_create(&pt1,NULL,pt1_func,NULL);  
    44.     pthread_create(&pt2,NULL,pt2_func,NULL);  
    45.     pthread_join(pt1,NULL);  
    46.     pthread_join(pt2,NULL);  
    47.   
    48.     printf("main..\n");  
    49.   
    50.     return 0;  
    51. }  
    (b)无名信号量在相关进程间的同步说是相关进程,是因为本程序中共有2个进程,其中一个是另外一个的子进程(由fork产生)的。本来对于fork来说,子进程只继承了父进程的代码副本,mutex理应在父子进程中是相互独立的两个变量,但由于在初始化mutex的时候,由pshared = 1指定了mutex处于共享内存区域,所以此时mutex变成了父子进程共享的一个变量。此时,mutex就可以用来同步相关进程了。
    [cpp]  view plain  copy
     print ?
    1. #include <stdio.h>  
    2. #include <sys/types.h>  
    3. #include <unistd.h>  
    4. #include <semaphore.h>  
    5.   
    6. int main(void)  
    7. {  
    8.     sem_t sem_id;  
    9. //第二个参数为1  
    10.     sem_init(&sem_id,1,1);  
    11.   
    12.     if(0 == fork()){  
    13.         while(1){  
    14.             sem_wait(&sem_id);  
    15.             printf("This child \n");  
    16.             sleep(2);  
    17.             sem_post(&sem_id);  
    18.         }  
    19.     }  
    20.   
    21.     while(1){  
    22.         sem_wait(&sem_id);  
    23.         printf("This father\n");  
    24.         sleep(2);  
    25.         sem_post(&sem_id);  
    26.     }  
    27.   
    28.     return 0;  
    29. }  
    2.有名信号量
    有名信号量的特点是把信号量的值保存在文件中。
    这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关进程。
    (a)有名信号量能在进程间共享的原因由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,当然文件里面保存的有名信号量值就共享了。
    (b)有名信号量相关函数说明有名信号量在使用的时候,和无名信号量共享sem_wait和sem_post函数。
    区别是有名信号量使用sem_open代替sem_init,另外在结束的时候要像关闭文件一样去关闭这个有名信号量。
     (1)打开一个已存在的有名信号量,或创建并初始化一个有名信号量。一个单一的调用就完成了信号量的创建、初始化和权限的设置。
    sem_t *sem_open(const char *name,  int oflag, mode_t mode , int value);
    name是文件的路径名;
    Oflag 有O_CREAT或O_CREAT|EXCL两个取值;
    mode_t控制新的信号量的访问权限;
    Value指定信号量的初始化值。
      注意:这里的name不能写成/tmp/aaa.sem这样的格式,因为在linux下,sem都是创建在/dev/shm目录下。你可以将name写成“/mysem”或“mysem”,创建出来的文件都是“/dev/shm/sem.mysem”,千万不要写路径。也千万不要写“/tmp/mysem”之类的。
    当oflag = O_CREAT时,若name指定的信号量不存在时,则会创建一个,而且后面的mode和value参数必须有效。若name指定的信号量已存在,则直接打开该信号量,同时忽略mode和value参数。
    当oflag = O_CREAT|O_EXCL时,若name指定的信号量已存在,该函数会直接返回error。
    (2) 一旦你使用了一信号量,销毁它们就变得很重要。
    在做这个之前,要确定所有对这个有名信号量的引用都已经通过sem_close()函数关闭了,然后只需在退出或是退出处理函数中调用sem_unlink()去删除系统中的信号量,注意如果有任何的处理器或是线程引用这个信号量,sem_unlink()函数不会起到任何的作用。
    也就是说,必须是最后一个使用该信号量的进程来执行sem_unlick才有效。因为每个信号灯有一个引用计数器记录当前的打开次数,sem_unlink必须等待这个数为0时才能把name所指的信号灯从文件系统中删除。也就是要等待最后一个sem_close发生。
    (c)有名信号量在无相关进程间的同步
    前面已经说过,有名信号量是位于共享内存区的,那么它要保护的资源也必须是位于共享内存区,只有这样才能被无相关的进程所共享。
    在下面这个例子中,服务进程和客户进程都使用shmget和shmat来获取得一块共享内存资源。然后利用有名信号量来对这块共享内存资源进行互斥保护。
    File1: server.c 
    [cpp]  view plain  copy
     print ?
    1. #include <stdio.h>  
    2. #include <sys/shm.h>  
    3. #include <semaphore.h>  
    4. #include <sys/types.h>  
    5. #include <fcntl.h>  
    6. #include <sys/ipc.h>  
    7.   
    8. #define SHMSZ 27  
    9. #define SEM_NAME "vik3"  
    10.   
    11. int main(void)  
    12. {  
    13.     char ch;  
    14.     int shmid;  
    15.     key_t key;  
    16.     char *shm, *s;  
    17.     sem_t *mutex;  
    18.   
    19.     key = 1000;  
    20.   
    21.     mutex = sem_open(SEM_NAME,O_CREAT,0644,1);  
    22.     if(mutex == SEM_FAILED){  
    23.         printf("unable to create semaphore");  
    24.         sem_unlink(SEM_NAME);  
    25.         return -1;  
    26.     }  
    27.   
    28.     shmid = shmget(key, SHMSZ, IPC_CREAT|0666);  
    29.     if(shmid < 0){  
    30.         printf("failure in shmget");  
    31.         return -1;  
    32.     }  
    33.   
    34.     shm = shmat(shmid,NULL,0);  
    35.     s = shm;  
    36.   
    37.     for(ch ='A'; ch <= 'Z'; ch++){  
    38.         sem_wait(mutex);  
    39.         *s++ = ch;  
    40.         sem_post(mutex);  
    41.     }  
    42.   
    43.     while(*shm != '*'){  
    44.         sleep(1);  
    45.     }  
    46.   
    47.     sem_close(mutex);  
    48.     sem_unlink(SEM_NAME);  
    49.     shmctl(shmid, IPC_RMID, 0);  
    50.   
    51.     return -1;  
    52. }  
    File 2: client.c
    [cpp]  view plain  copy
     print ?
    1. #include <sys/types.h>  
    2. #include <sys/shm.h>  
    3. #include <stdio.h>  
    4. #include <semaphore.h>  
    5.   
    6. #define SHMSZ 27  
    7. #define SEM_NAME "vik3"  
    8.   
    9. int main(void)  
    10. {  
    11.     char ch;  
    12.     int shmid;  
    13.     key_t key;  
    14.     char *shm,*s;  
    15.     sem_t *mutex;  
    16.   
    17.     key = 1000;  
    18.   
    19.     mutex = sem_open(SEM_NAME,0,0644,0);  
    20.     if(mutex == SEM_FAILED){  
    21.         printf("unable to execute semaphore\n");  
    22.         sem_close(mutex);  
    23.         return -1;  
    24.     }  
    25.   
    26.     shmid = shmget(key,SHMSZ,0666);  
    27.     if(shmid < 0){  
    28.         printf("failure in shmget");  
    29.         return -1;  
    30.     }  
    31.   
    32.     shm = shmat(shmid,NULL,0);  
    33.     //s = shm;  
    34.   
    35.     for(s = shm; *s != NULL; s++){  
    36.         sem_wait(mutex);  
    37.         putchar(*s);  
    38.         sem_post(mutex);  
    39.     }  
    40.     putchar('\n');  
    41.     *shm = '*';  
    42.   
    43.     sem_close(mutex);  
    44.     sem_unlink(SEM_NAME);  
    45.     shmctl(shmid, IPC_RMID, 0);  
    46.   
    47.     return -1;  
    48. }  
    SYSTEM V信号量
    这是信号量值的集合,而不是单个信号量。相关的信号量操作函数由<sys/ipc.h>引用。
    1.信号量结构体
    内核为每个信号量集维护一个信号量结构体,可在<sys/sem.h>找到该定义:
    struct semid_ds {
    struct ipc_perm sem_perm; /* 信号量集的操作许可权限*/
    struct sem *sem_base; /* 某个信号量sem结构数组的指针,当前信号量集
    中的每个信号量对应其中一个数组元素*/
    ushort sem_nsems; /* sem_base 数组的个数*/
    time_t sem_otime; /* 最后一次成功修改信号量数组的时间*/
    time_t sem_ctime; /* 成功创建时间*/
    };
    struct sem {
    ushort semval; /* 信号量的当前值 */
    short sempid; /* 最后一次返回该信号量的进程ID号 */
    ushort semncnt; /* 等待semval大于当前值的进程个数 */
    ushort semzcnt; /* 等待semval变成0的进程个数 */
    };
    2.常见的SYSTEM V信号量函数
    (a)关键字和描述符SYSTEM V信号量是SYSTEM V IPC(即SYSTEM V进程间通信)的组成部分,其他的有SYSTEM V消息队列,SYSTEM V共享内存。而关键字和IPC描述符无疑是它们的共同点,也使用它们,就不得不先对它们进行熟悉。这里只对SYSTEM V信号量进行讨论。IPC描述符相当于引用ID号,要想使用SYSTEM V信号量(或MSG、SHM),就必须用IPC描述符来调用信号量。而IPC描述符是内核动态提供的(通过semget来获取),用户无法让服务器和客户事先认可共同使用哪个描述符,所以有时候就需要到关键字KEY来定位描述符。
    某个KEY只会固定对应一个描述符(这项转换工作由内核完成),这样假如服务器和客户事先认可共同使用某个KEY,那么大家就都能定位到同一个描述符,也就能定位到同一个信号量,这样就达到了SYSTEM V信号量在进程间共享的目的。
    (b)创建和打开信号量
    int semget(key_t  key, int  nsems, int  oflag)
    (1) nsems>0  : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更改。
    (2) nsems==0 : 访问一个已存在的集合
    (3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。
    (4) 创建成功后信号量结构被设置:
        .sem_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID
        .oflag 参数中的读写权限位存入sem_perm.mode
        .sem_otime 被置为0,sem_ctime被设置为当前时间
        .sem_nsems 被置为nsems参数的值
        该集合中的每个信号量不初始化,这些结构是在semctl,用参数SET_VAL,SETALL初始化的。
        semget函数执行成功后,就产生了一个由内核维持的类型为semid_ds结构体的信号量集,返回semid就是指向该信号量集的引索。
    (c)关键字的获取
           有多种方法使客户机和服务器在同一IPC结构上会合:
    (1) 服务器可以指定关键字IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处(例如一个文件)以便客户机取用。关键字IPC_PRIVATE保证服务器创建一个新IPC结构。这种技术的缺点是:服务器要将整型标识符写到文件中,然后客户机在此后又要读文件取得此标识符。
    IPC_PRIVATE关键字也可用于父、子关系进程。父进程指定IPC_PRIVATE创建一个新IPC结构,所返回的标识符在fork后可由子进程使用。子进程可将此标识符作为exec函数的一个参数传给一个新程序。
    (2) 在一个公用头文件中定义一个客户机和服务器都认可的关键字。然后服务器指定此关键字创建一个新的IPC结构。这种方法的问题是该关键字可能已与一个IPC结构相结合,在此情况下,get函数(msgget、semget或shmget)出错返回。服务器必须处理这一错误,删除已存在的IPC结构,然后试着再创建它。当然,这个关键字不能被别的程序所占用。
    (3) 客户机和服务器认同一个路径名和课题I D(课题I D是0 ~ 2 5 5之间的字符值) ,然后调用函数ftok将这两个值变换为一个关键字。这样就避免了使用一个已被占用的关键字的问题。
    使用ftok并非高枕无忧。有这样一种例外:服务器使用ftok获取得一个关键字后,该文件就被删除了,然后重建。此时客户端以此重建后的文件来ftok所获取的关键字就和服务器的关键字不一样了。所以一般商用的软件都不怎么用ftok。
    一般来说,客户机和服务器至少共享一个头文件,所以一个比较简单的方法是避免使用ftok,而只是在该头文件中存放一个大家都知道的关键字。    
    (d)设置信号量的值(PV操作)
    某个KEY只会固定对应一个描述符(这项转换工作由内核完成),这样假如服务器和客户事先认可共同使用某个KEY,那么大家就都能定位到同一个描述符,也就能定位到同一个信号量,这样就达到了SYSTEM V信号量在进程间共享的目的。
    (b)创建和打开信号量
    int semget(key_t  key, int  nsems, int  oflag)
    (1) nsems>0  : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更改。
    (2) nsems==0 : 访问一个已存在的集合
    (3) 返回的是一个称为信号量标识符的整数,semop和semctl函数将使用它。
    (4) 创建成功后信号量结构被设置:
        .sem_perm 的uid和gid成员被设置成的调用进程的有效用户ID和有效组ID
        .oflag 参数中的读写权限位存入sem_perm.mode
        .sem_otime 被置为0,sem_ctime被设置为当前时间
        .sem_nsems 被置为nsems参数的值
        该集合中的每个信号量不初始化,这些结构是在semctl,用参数SET_VAL,SETALL初始化的。
        semget函数执行成功后,就产生了一个由内核维持的类型为semid_ds结构体的信号量集,返回semid就是指向该信号量集的引索。
    (c)关键字的获取
           有多种方法使客户机和服务器在同一IPC结构上会合:
    (1) 服务器可以指定关键字IPC_PRIVATE创建一个新IPC结构,将返回的标识符存放在某处(例如一个文件)以便客户机取用。关键字IPC_PRIVATE保证服务器创建一个新IPC结构。这种技术的缺点是:服务器要将整型标识符写到文件中,然后客户机在此后又要读文件取得此标识符。
    IPC_PRIVATE关键字也可用于父、子关系进程。父进程指定IPC_PRIVATE创建一个新IPC结构,所返回的标识符在fork后可由子进程使用。子进程可将此标识符作为exec函数的一个参数传给一个新程序。
    (2) 在一个公用头文件中定义一个客户机和服务器都认可的关键字。然后服务器指定此关键字创建一个新的IPC结构。这种方法的问题是该关键字可能已与一个IPC结构相结合,在此情况下,get函数(msgget、semget或shmget)出错返回。服务器必须处理这一错误,删除已存在的IPC结构,然后试着再创建它。当然,这个关键字不能被别的程序所占用。
    (3) 客户机和服务器认同一个路径名和课题I D(课题I D是0 ~ 2 5 5之间的字符值) ,然后调用函数ftok将这两个值变换为一个关键字。这样就避免了使用一个已被占用的关键字的问题。
    使用ftok并非高枕无忧。有这样一种例外:服务器使用ftok获取得一个关键字后,该文件就被删除了,然后重建。此时客户端以此重建后的文件来ftok所获取的关键字就和服务器的关键字不一样了。所以一般商用的软件都不怎么用ftok。
    一般来说,客户机和服务器至少共享一个头文件,所以一个比较简单的方法是避免使用ftok,而只是在该头文件中存放一个大家都知道的关键字。    
    (d)设置信号量的值(PV操作)
    int semop(int semid, struct sembuf *opsptr, size_t nops);
    (1) semid: 是semget返回的semid
    (2)opsptr: 指向信号量操作结构数组
    (3) nops : opsptr所指向的数组中的sembuf结构体的个数
    struct sembuf {
    short sem_num; // 要操作的信号量在信号量集里的编号,
    short sem_op; // 信号量操作
    short sem_flg; // 操作表示符
    };
    (4) 若sem_op 是正数,其值就加到semval上,即释放信号量控制的资源
        若sem_op 是0,那么调用者希望等到semval变为0,如果semval是0就返回;
    若sem_op 是负数,那么调用者希望等待semval变为大于或等于sem_op的绝对值
    例如,当前semval为2,而sem_op = -3,那么怎么办?
    注意:semval是指semid_ds中的信号量集中的某个信号量的值
    (5) sem_flg
        SEM_UNDO     由进程自动释放信号量
        IPC_NOWAIT    不阻塞   
    到这里,读者肯定有个疑惑:semop希望改变的semval到底在哪里?我们怎么没看到有它的痕迹?其实,前面已经说明了,当使用semget时,就产生了一个由内核维护的信号量集(当然每个信号量值即semval也是只由内核才能看得到了),用户能看到的就是返回的semid。内核通过semop函数的参数,知道应该去改变semid所指向的信号量的哪个semval。
    [cpp]  view plain  copy
     print ?
    1. #include <stdio.h>   
    2. #include <stdlib.h>   
    3. #include <sys/types.h>   
    4. #include <sys/ipc.h>   
    5. #include <sys/sem.h>   
    6. #include <sys/stat.h>   
    7. #include <fcntl.h>   
    8.    
    9. union semun   
    10. {   
    11.     int val;   
    12.     struct semid_ds *buf;   
    13.     unsigned short int *array;   
    14.     struct seminfo *__buf;   
    15. };   
    16.    
    17. int main(void)   
    18. {   
    19.     char* buf_child[]={"this""is""the""child""process"};   
    20.     char* buf_father[]={"father""say""hello""to""child"};   
    21.     int i = 0, semid, fd;   
    22.     pid_t pid;   
    23.     struct sembuf sb; //信号量操作  
    24.     union semun sem;   
    25.     semid = semget(1000, 2, 0666 | IPC_CREAT); //申请信号量组,包含2个信号量  
    26.    
    27.     sem.val = 0;   
    28.     semctl(semid, 0, SETVAL, sem); //初始化0号信号量为0  
    29.     sem.val = 1;   
    30.     semctl(semid, 1, SETVAL, sem); //初始化1号信号量为1  
    31.    
    32.     fd=open("tmp",O_CREAT|O_TRUNC|O_WRONLY,0666);   
    33.    
    34.     pid = fork();   
    35.     switch (pid) {   
    36.         case -1:   
    37.             perror("fork fail");   
    38.             break;   
    39.         case 0: /* child consume */   
    40.             srand((unsigned int)getpid());   
    41.             while (i < 5) {   
    42.                 sb.sem_num = 1; //将1号信号量  
    43.                 sb.sem_op = -1; //减1  
    44.                 sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT;   
    45.                 semop(semid, &sb, 1);   
    46.    
    47.                 write(fd,buf_child[i], strlen(buf_child[i]));   
    48.                 sleep(2);   
    49.                 write(fd,&" ", 1);   
    50.                 i++;   
    51.    
    52.                 sb.sem_num = 0; //将0号信号量  
    53.                 sb.sem_op = 1;  //加1  
    54.                 sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT;   
    55.                 semop(semid, &sb, 1); //操作信号量  
    56.             }   
    57.             break;   
    58.         default:/* parent production  */   
    59.             srand((unsigned int)getpid());   
    60.             while (i < 5) {   
    61.                 sb.sem_num = 0; //将0号信号量  
    62.                 sb.sem_op = -1; //减1  
    63.                 sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT;   
    64.                 semop(semid, &sb, 1); //操作信号量  
    65.    
    66.                 write(fd,buf_father[i], strlen(buf_father[i]));   
    67.                 sleep(2);   
    68.                 write(fd,&" ", 1);   
    69.                 i++;   
    70.    
    71.                 sb.sem_num = 1;   
    72.                 sb.sem_op = 1;   
    73.                 sb.sem_flg = sb.sem_flg & ~IPC_NOWAIT;   
    74.                 semop(semid, &sb, 1);   
    75.             }   
    76.             break;   
    77.     }   
    78.     return 0;   
    79. }  
    (e)对信号集实行控制操作(semval的赋值等)
    int semctl(int semid, int semum, int cmd, ../* union semun arg */); 
    semid是信号量集合;
    semnum是信号在集合中的序号;
    semum是一个必须由用户自定义的结构体,在这里我们务必弄清楚该结构体的组成:
    union semun
    {
    int val; // cmd == SETVAL
    struct semid_ds *buf // cmd == IPC_SET或者cmd == IPC_STAT
    ushort *array; // cmd == SETALL,或cmd = GETALL
    };
    val只有cmd ==SETVAL时才有用,此时指定的semval = arg.val。
    注意:当cmd == GETVAL时,semctl函数返回的值就是我们想要的semval。千万不要以为指定的semval被返回到arg.val中。
        array指向一个数组,当cmd==SETALL时,就根据arg.array来将信号量集的所有值都赋值;当cmd ==GETALL时,就将信号量集的所有值返回到arg.array指定的数组中。
    buf指针只在cmd==IPC_STAT或IPC_SET时有用,作用是semid所指向的信号量集(semid_ds机构体)。一般情况下不常用,这里不做谈论。
    另外,cmd == IPC_RMID还是比较有用的。

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>
    #include <stdio.h> 
    static int nsems;
    static int semflg;
    static int semid;
    int errno=0; 
    union semun {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    }arg; 
    int main()
    {
    struct sembuf sops[2]; //要用到两个信号量,所以要定义两个操作数组
    int rslt;
    unsigned short argarray[80]; 
    arg.array = argarray;
    semid = semget(IPC_PRIVATE, 2, 0666);
    if(semid < 0 )
    {
    printf("semget failed. errno: %d\n", errno);
    exit(0);

    //获取0th信号量的原始值
    rslt = semctl(semid, 0, GETVAL);
    printf("val = %d\n",rslt);
    //初始化0th信号量,然后再读取,检查初始化有没有成功
    arg.val = 1; // 同一时间只允许一个占有者
    semctl(semid, 0, SETVAL, arg);
    rslt = semctl(semid, 0, GETVAL);
    printf("val = %d\n",rslt); 
    sops[0].sem_num = 0;
    sops[0].sem_op = -1;
    sops[0].sem_flg = 0;
    sops[1].sem_num = 1;
    sops[1].sem_op = 1;
    sops[1].sem_flg = 0;
    rslt=semop(semid, sops, 1); //申请0th信号量,尝试锁定
    if (rslt < 0 )
    {
    printf("semop failed. errno: %d\n", errno);
    exit(0);
    }
    //可以在这里对资源进行锁定
    sops[0].sem_op = 1;
    semop(semid, sops, 1); //释放0th信号量
    rslt = semctl(semid, 0, GETVAL);
    printf("val = %d\n",rslt); 
    rslt=semctl(semid, 0, GETALL, arg);
    if (rslt < 0)
    {
    printf("semctl failed. errno: %d\n", errno);
    exit(0);

    printf("val1:%d val2: %d\n",(unsigned int)argarray[0],(unsigned int)argarray[1]);
    if(semctl(semid, 1, IPC_RMID) == -1)
    {
    Perror(“semctl failure while clearing reason”);
    }
    return(0);
    }
    生产者与消费者问题
    1.问题描述:
    有一个长度为N的缓冲池为生产者和消费者所共有,只要缓冲池未满,生产者便可将消息送入缓冲池;只要缓冲池未空,消费者便可从缓冲池中取走一个消息。生产者往缓冲池放信息的时候,消费者不可操作缓冲池,反之亦然。
    2.使用多线程和信号量解决该经典问题的互斥
    [cpp]  view plain  copy
     print ?
    1. <pre name="code" class="cpp">#include <stdio.h>  
    2. #include <pthread.h>  
    3. #include <semaphore.h>  
    4.   
    5. #define BUFF_SIZE 10  
    6. char buffer[BUFF_SIZE];  
    7. char count;  
    8. sem_t sem_mutex;  
    9. sem_t p_sem_mutex;  
    10. sem_t c_sem_mutex;  
    11.   
    12. void *p_funp(void *argc)  
    13. {  
    14.     sem_wait(&p_sem_mutex);  
    15.     sem_wait(&sem_mutex);  
    16.   
    17.   
    18.     count ++;  
    19.   
    20.   
    21.     if(count < BUFF_SIZE)  
    22.         sem_post(&p_sem_mutex);  
    23.     if(count > 0)  
    24.         sem_post(&c_sem_mutex);  
    25.     sem_post(&sem_mutex);  
    26. }  
    27.   
    28. void *c_func(void *arg)  
    29. {  
    30.     sem_wait(&c_sem_mutex);  
    31.     sem_wait(&sem_mutex);  
    32.   
    33.   
    34.     count--;  
    35.   
    36.   
    37.     if(count > 0)  
    38.         sem_post(&c_sem_mutex);  
    39.     if(count < BUFF_SIZE)  
    40.         sem_post(&p_sem_mutex);  
    41.   
    42.   
    43.     sem_post(&sem_mutex);  
    44. }  
    45.   
    46. int main(void)  
    47. {  
    48.     pthread_t pid1,pid2;  
    49.   
    50.     sem_init(&sem_mutex,0,1);  
    51.     sem_init(&p_sem_mutex,0,1);  
    52.     sem_init(&c_sem_mutex,0,0);  
    53.   
    54.     pthread_create(&pid1,NULL,p_func,NULL);  
    55.     pthread_create(&pid2,NULL,c_func,NULL);  
    56.   
    57.     pthread_join(pid1,NULL);  
    58.     pthread_join(pid2,NULL);  
    59.   
    60.     sem_destory(&sem_mutex);  
    61.     sem_destory(&c_sem_mutex);  
    62.     sem_destory(&p_sem_mutex);  
    63.   
    64.     return 0;  
    65. }  
    66. </pre><br>  
    67. <br>  
    68. <pre></pre>  
    69. <br>  
    70. <p></p>  
    展开全文
  • linux c 信号量详解

    千次阅读 2014-03-04 00:58:10
    信号量 当我们在多用户系统,多进程系统,或是两者混合的系统中使用线程操作编写程序时,我们经常会发现我们有段临界代码,在此处我们需要保证一个进程(或是一个线程的执行)需要排他的访问一个资源。 信号量有一...

    信号量

    当我们在多用户系统,多进程系统,或是两者混合的系统中使用线程操作编写程序时,我们经常会发现我们有段临界代码,在此处我们需要保证一个进程(或是一个线程的执行)需要排他的访问一个资源。
    信号量有一个复杂的编程接口。幸运的是,我们可以很容易的为自己提供一个对于大多数的信号量编程问题足够高效的简化接口。
    为了阻止多个程序同时访问一个共享资源所引起的问题,我们需要一种方法生成并且使用一个标记从而保证在临界区部分一次只有一个线程执行。线程相关的方法,我们可以使用互斥或信号量来控制一个多线程程序对于临界区的访问。


    编写通用目的的代码保证一个程序排他的访问一个特定的资源是十分困难的,尽管有一个名为Dekker的算法解决方法。不幸的是,这个算法依赖于"忙等待" 或是"自旋锁",即一个进程的连续运行需要等待一个内存地址发生改变。在一个多任务环境中,例如Linux,这是对CPU资源的无谓浪费。如果硬件支持, 这样的情况就要容易得多,通常以特定CPU指令的形式来支持排他访问。硬件支持的例子可以是访问指令与原子方式增加寄存器值,从而在读取/增加/写入的操 作之间就不会有其他的指令运行。

    我们已经了解到的一个要行的解决方法就是使用O_EXCL标记调用open函数来创建文件,这提供了原子方式的文件创建。这会使得一个进程成功的获得一个标记:新创建的文件。这个方法可以用于简单的问题,但是对于复杂的情况就要显得烦琐与低效了。

    当Dijkstr引入信号量的概念以后,并行编程领域前进了一大步。正如我们在第12章所讨论的,信号量是一个特殊的变量,他是一个整数,并且只有两个操 作可以使得其值增加:等待(wait)与信号(signal)。因为在Linux与UNIX编程中,"wait"与"signal"已经具有特殊的意义 了,我们将使用原始概念:
    用于等待(wait)的P(信号量变量)
    用于信号(signal)的V(信号量变量)

    这两字母来自等待(passeren:通过,如同临界区前的检测点)与信号(vrjgeven:指定或释放,如同释放临界区的控制权)的荷兰语。有时我们也会遇到与信号量相关的术语"up"与"down",来自于信号标记的使用。

    信号量定义
     

    最简单的信号量是一个只有0与1两个值的变量,二值信号量。这是最为通常的形式。具有多个正数值的信号量被称之为通用信号量。在本章的其余部分,我们将会讨论二值信号量。

    P与V的定义出奇的简单。假定我们有一个信号量变量sv,两个操作定义如下:

    P(sv)    如果sv大于0,减小sv。如果sv为0,挂起这个进程的执行。
    V(sv)    如果有进程被挂起等待sv,使其恢复执行。如果没有进行被挂起等待sv,增加sv。

    信号量的另一个理解方式就是当临界区可用时信号量变量sv为true,当临界区忙时信号量变量被P(sv)减小,从而变为false,当临界区再次可用时 被V(sv)增加。注意,简单的具有一个我们可以减小或是增加的通常变量并不足够,因为我们不能用C,C++或是其他的编程语言来表述生成信号,进行原子 测试来确定变量是否为true,如果是则将其变为false。这就是使得信号量操作特殊的地方。

    一个理论例子 

    我们可以使用一个简单的理论例子来了解一下信号量是如何工作的。假设我们有两个进程proc1与proc2,这两个进程会在他们执行的某一时刻排他的访问 一个数据库。我们定义一个单一的二值信号量,sv,其初始值为1并且可以为两个进程所访问。两个进程然后需要执行同样的处理来访问临界区代码;实际上,这 两个进程可以是同一个程序的不同调用。

    这两个进程共享sv信号量变量。一旦一个进程已经执行P(sv)操作,这个进程就可以获得信号量并且进入临界区。第二个进程就会被阻止进行临界区,因为当他尝试执行P(sv)时,他就会等待,直到第一个进程离开临界区并且执行V(sv)操作来释放信号量。

    所需要的过程如下:

    semaphore sv = 1;
    loop forever {
        P(sv);
        critical code section;//临界代码段
        V(sv);
        noncritical code section;
    }

    这段代码出奇的简单,因为P操作与V操作是十分强大的。图14-1显示了P操作与V操作如何成为进行临界区代码的门槛。

    Linux信号量工具 

    现在我们已经了解了什么是信号量以及他们在理论上是如何工作的,现在我们可以来了解一下这些特性在Linux中是如何实现的。信号量函数接口设计十分精 细,并且提供了比通常所需要的更多的实用性能。所有的Linux信号量函数在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。乍看 起来,这似乎使得事情变得更为复杂,但是在一个进程需要锁住多个资源的复杂情况下,在信号量数组上进行操作将是一个极大的优点。在这一章,我们将会关注于 使用单一信号量,因为在大多数情况下,这正是我们需要使用的。

    信号量函数定义如下:

    #include <sys/sem.h>
    int semctl(int sem_id, int sem_num, int command, ...);
    int semget(key_t key, int num_sems, int sem_flags);
    int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

    事实上,为了获得我们特定操作所需要的#define定义,我们需要在包含sys/sem.h文件之前通常需要包含sys/types.h与sys/ipc.h文件。而在某些情况下,这并不是必须的。

    因为我们会依次了解每一个函数,记住,这些函数的设计是用于操作信号量值数组的,从而会使用其操作向比单个信号量所需要的操作更为复杂。

    注意,key的作用类似于一个文件名,因为他表示程序也许会使用或是合作所用的资源。相类似的,由semget所返回的并且为其他的共享内存函数所用的标 识符与由fopen函数所返回 的FILE *十分相似,因为他被进程用来访问共享文件。而且与文件类似,不同的进程会有不同的信号量标识符,尽管他们指向相同的信号量。key与标识符的用法对于在 这里所讨论的所有IPC程序都是通用的,尽管每一个程序会使用独立的key与标识符。

    semget 

    semget函数创建一个新的信号量或是获得一个已存在的信号量键值。

    int semget(key_t key, int num_sems, int sem_flags);

    第一个参数key是一个用来允许不相关的进程访问相同信号量的整数值。所有的信号量是为不同的程序通过提供一个key来间接访问的,对于每一个信号量系统 生成一个信号量标识符。信号量键值只可以由semget获得,所有其他的信号量函数所用的信号量标识符都是由semget所返回的。

    还有一个特殊的信号量key值,IPC_PRIVATE(通常为0),其作用是创建一个只有创建进程可以访问的信号量。这通常并没有有用的目的,而幸运的是,因为在某些Linux系统上,手册页将IPC_PRIVATE并没有阻止其他的进程访问信号量作为一个bug列出。

    num_sems参数是所需要的信号量数目。这个值通常总是1。

    sem_flags参数是一个标记集合,与open函数的标记十分类似。低九位是信号的权限,其作用与文件权限类似。另外,这些标记可以与 IPC_CREAT进行或操作来创建新的信号量。设置IPC_CREAT标记并且指定一个已经存在的信号量键值并不是一个错误。如果不需 要,IPC_CREAT标记只是被简单的忽略。我们可以使用IPC_CREAT与IPC_EXCL的组合来保证我们可以获得一个新的,唯一的信号量。如果 这个信号量已经存在,则会返回一个错误。

    如果成功,semget函数会返回一个正数;这是用于其他信号量函数的标识符。如果失败,则会返回-1。

    semop 

    函数semop用来改变信号量的值:

    int semop(int sem_id, struct sembuf *sem_ops, size_t num_sem_ops);

    第一个参数,sem_id,是由semget函数所返回的信号量标识符。第二个参数,sem_ops,是一个指向结构数组的指针,其中的每一个结构至少包含下列成员:

    struct sembuf {
        short sem_num;
        short sem_op;
        short sem_flg;
    }

    第一个成员,sem_num,是信号量数目,通常为0,除非我们正在使用一个信号量数组。sem_op成员是信号量的变化量值。(我们可以以任何量改变信 号量值,而不只是1)通常情况下中使用两个值,-1是我们的P操作,用来等待一个信号量变得可用,而+1是我们的V操作,用来通知一个信号量可用。

    最后一个成员,sem_flg,通常设置为SEM_UNDO。这会使得操作系统跟踪当前进程对信号量所做的改变,而且如果进程终止而没有释放这个信号量, 如果信号量为这个进程所占有,这个标记可以使得操作系统自动释放这个信号量。将sem_flg设置为SEM_UNDO是一个好习惯,除非我们需要不同的行 为。如果我们确实变我们需要一个不同的值而不是SEM_UNDO,一致性是十分重要的,否则我们就会变得十分迷惑,当我们的进程退出时,内核是否会尝试清 理我们的信号量。

    semop的所用动作会同时作用,从而避免多个信号量的使用所引起的竞争条件。我们可以在手册页中了解关于semop处理更为详细的信息。

    semctl 

    semctl函数允许信号量信息的直接控制:

    int semctl(int sem_id, int sem_num, int command, ...);

    第一个参数,sem_id,是由semget所获得的信号量标识符。sem_num参数是信号量数目。当我们使用信号量数组时会用到这个参数。通常,如果 这是第一个且是唯一的一个信号量,这个值为0。command参数是要执行的动作,而如果提供了额外的参数,则是union semun,根据X/OPEN规范,这个参数至少包括下列参数:

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

    许多版本的Linux在头文件(通常为sem.h)中定义了semun联合,尽管X/Open确认说我们必须定义我们自己的联合。如果我们发现我们确实需 要定义我们自己的联合,我们可以查看semctl手册页了解定义。如果有这样的情况,建议使用手册页中提供的定义,尽管这个定义与上面的有区别。

    有多个不同的command值可以用于semctl。在这里我们描述两个会经常用到的值。要了解semctl功能的详细信息,我们应该查看手册页。

    这两个通常的command值为:

    SETVAL:用于初始化信号量为一个已知的值。所需要的值作为联合semun的val成员来传递。在信号量第一次使用之前需要设置信号量。
    IPC_RMID:当信号量不再需要时用于删除一个信号量标识。

    semctl函数依据command参数会返回不同的值。对于SETVAL与IPC_RMID,如果成功则会返回0,否则会返回-1。

    使用信号量 

    正如我们在前面部分的描述中所看到的,信号量操作是相当复杂的。这是最不幸的,因为使用临界区进行多进程或是多线程编程是一个十分困难的问题,而其拥有其自己复杂的编程接口也增加了编程负担。

    幸运的是,我们可以使用最简单的二值信号量来解决大多数需要信号量的问题。在我们的例子中,我们会使用所有的编程接口来创建一个非常简单的用于二值信号量的P
    与V类型接口。然后,我们会使用这个简单的接口来演示信号量如何工作。

    要试验信号量,我们将会使用一个简单的程序,sem1.c,这个程序我们可以多次调用。我们将会使用一个可选的参数来标识这个程序是负责创建信号量还是销毁信号量。

    我们使用两个不同字符的输出来标识进入与离开临界区。使用参数调用的程序会在进入与离开其临界区时输出一个X,而另一个程序调用会在进入与离开其临界区时输出一个O。因为在任何指定的时间内只有一个进程能够进入其临界区,所以所有X与O字符都是成对出现的。

    试验--信号量

    1 在#include语句之后,我们定义函数原型与全局变量,然后我们进入main函数。在这里使用semget函数调用创建信号量,这会返回一个信号量 ID。如果程序是第一次调用(例如,使用一个参数并且argc > 1来调用),程序就会调用set_semvalue来初始化信号量并且将op_char设置为X。

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>

    #include <sys/types.h>
    #include <sys/ipc.h>
    #include <sys/sem.h>

    #include "semun.h"

    static int set_semvalue(void);
    static void del_semvalue(void);
    static int semaphore_p(void);
    static int semaphore_v(void);

    static int sem_id;

    int main(int argc, char **argv)
    {
        int i;
        int pause_time;
        char op_char = 'O';

        srand((unsigned int)getpid());

        sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);

        if(argc > 1)
        {
            if(!set_semvalue())
            {
                fprintf(stderr, "Failed to initialize semaphore/n");
                exit(EXIT_FAILURE);
            }
            op_char = 'X';
            sleep(2);
        }
    2 然后我们使用一个循环代码进入并且离开临界区10次。此时会调用semaphore_p函数,这个函数会设置信号量并且等待程序进入临界区。
        for(i=0;i<10;i++)
        {
            if(!semaphore_p()) exit(EXIT_FAILURE);
            printf("%c", op_char); fflush(stdout);
            pause_time = rand() % 3;
            sleep(pause_time);
            printf("%c", op_char); fflush(stdout);
    3 在临界区之后,我们调用semaphore_v函数,在随机的等待之后再次进入for循环之后,将信号量设置为可用。在循环之后,调用del_semvalue来清理代码。
            if(!semaphore_v()) exit(EXIT_FAILURE);

            pause_time = rand() % 2;
            sleep(pause_time);
        }

        printf("/n%d - finished/n", getpid());

        if(argc > 1)
        {
            sleep(10);
            del_semvalue();
        }

        exit(EXIT_SUCCESS);
        }

    4 函数set_semvalue在一个semctl调用中使用SETVAL命令来初始化信号量。在我们使用信号量之前,我们需要这样做。

    static int set_semvalue(void)
    {
        union semun sem_union;

        sem_union.val = 1;
        if(semctl(sem_id, 0, SETVAL, sem_union) == -1) return 0;
        return 1;
    }
    5 del_semvalue函数几乎具有相同的格式,所不同的是semctl调用使用IPC_RMID命令来移除信号量ID:

    static void del_semvalue(void)
    {
        union semun sem_union;

        if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
            fprintf(stderr, "Failed to delete semaphore/n");
    }

    6 semaphore_p函数将信号量减1(等待):

    static int semaphore_p(void)
    {
        struct sembuf sem_b;

        sem_b.sem_num = 0;
        sem_b.sem_op = -1;
        sem_b.sem_flag = SEM_UNDO;
        if(semop(sem_id, &sem_b, 1) == -1)
        {
            fprintf(stderr, "semaphore_p failed/n");
            return 0;
        }
        return 1;
    }

    7 semaphore_v函数将sembuf结构的sem_op部分设置为1,从而信号量变得可用。

    static int semaphore_v(void)
    {
        struct sembuf sem_b;

        sem_b.sem_num = 0;
        sem_b.sem_op = 1;
        sem_b.sem_flag = SEM_UNDO;
        if(semop(sem_id, &sem_b, 1) == -1)
        {
            fprintf(stderr, "semaphore_v failed/n");
            return 0;
        }
        return 1;
    }

    注意,这个简单的程序只有每个程序有一个二值信号量,尽管如果我们需要多个信号量,我们可以扩展这个程序来传递多个信号量变量。通常,一个简单的二值信号量就足够了。

    我们可以通过多次调用这个程序来测试我们的程序。第一次,我们传递一个参数来通知程序他并不负责创建与删除信号量。另一次调用没有传递参数。

    下面是两次调用的示例输出结果:

    $ ./sem1 1 &
    [1] 1082
    $ ./sem1
    OOXXOOXXOOXXOOXXOOXXOOOOXXOOXXOOXXOOXXXX
    1083 - finished
    1082 - finished
    $

    正如我们所看到了,O与X是成对出现的,表明临界区部分被正确的处理了。如果这个程序在我们的系统上不能正常运行,也许我们需要在调用程序之前使用命令stty -tostop来保证生成tty输出的后台程序不会引起信号生成。

    工作原理 

    这个程序由我们选择使用semget函数所获得的键生成一个信号量标识开始。IPC_CREAT标记会使得如果需要的时候创建一个信号量。

    如果这个程序有参数,他负责使用我们的set_semvalue函数来初始化信号量,这是更为通用的semctl函数的一个简化接口。同时,他也使用所提 供的参数来决定要输出哪一个字符。sleep只是简单的使得我们在这个程序执行多次之前有时间调用程序的另一个拷贝。在程序中我们使用srand与 rand来引入一些伪随机计数。

    这个程序循环十次,在其临界区与非临界区等待一段随机的时间。临界区代码是通过调用我们的semaphore_p与semaphore_v函数来进行保护的,这两个函数是更为通用的semop函数的简化接口。

    在删除信号量之前,使用参数调用的程序拷贝会等待其他的调用结束。如果信号量没有删除,他就会继续存在于系统中,尽管已经没有程序再使用他。在实际的程序 中,保证我们没有遗留信号是十分重要的。在我们下一次运行程序时,遗留的信号量会引起问题,而且信号量是限制资源,我们必须小心使用。

    展开全文
  • linux信号量函数详解

    千次阅读 2012-01-31 19:46:36
     可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集: 系统调用:semget(); 原型:intsemget(key_t key,int nsems,int semflg); 返回值:如果成功,则返回信号量集的IPC标识符。...
    semget()

         可以使用系统调用semget()创建一个新的信号量集,或者存取一个已经存在的信号量集:
    系统调用:semget();
    原型:intsemget(key_t key,int nsems,int semflg);
    返回值:如果成功,则返回信号量集的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中定义的:
    #defineSEMMSL32/*<=512maxnumofsemaphoresperid*/
    下面是一个打开和创建信号量集的程序:
    intopen_semaphore_set(key_t keyval,int numsems)
    {
    intsid;
    if(!numsems)
    return(-1);
    if((sid=semget(mykey,numsems,IPC_CREAT|0660))==-1)
    {
    return(-1);
    }
    return(sid);
    }
    };
    ==============================================================
    semop()

    系统调用:semop();
    调用原型:int semop(int semid,struct sembuf*sops,unsign ednsops);
    返回值:0,如果成功。-1,如果失败:errno=E2BIG(nsops大于最大的ops数目)
    EACCESS(权限不够)
    EAGAIN(使用了IPC_NOWAIT,但操作不能继续进行)
    EFAULT(sops指向的地址无效)
    EIDRM(信号量集已经删除)
    EINTR(当睡眠时接收到其他信号)
    EINVAL(信号量集不存在,或者semid无效)
    ENOMEM(使用了SEM_UNDO,但无足够的内存创建所需的数据结构)
    ERANGE(信号量值超出范围)
        第一个参数是关键字值。第二个参数是指向将要操作的数组的指针。第三个参数是数组中的操作的个数。参数sops指向由sembuf组成的数组。此数组是在linux/sem.h中定义的:
    /*semop systemcall takes an array of these*/
    structsembuf{
    ushortsem_num;/*semaphore index in array*/
    shortsem_op;/*semaphore operation*/
    shortsem_flg;/*operation flags*/
    sem_num将要处理的信号量的个数。
    sem_op要执行的操作。
    sem_flg操作标志。
        如果sem_op是负数,那么信号量将减去它的值。这和信号量控制的资源有关。如果没有使用IPC_NOWAIT,那么调用进程将进入睡眠状态,直到信号 量控制的资源可以使用为止。如果sem_op是正数,则信号量加上它的值。这也就是进程释放信号量控制的资源。最后,如果sem_op是0,那么调用进程 将调用sleep(),直到信号量的值为0。这在一个进程等待完全空闲的资源时使用。
    ===============================================================
    semctl()

    系统调用:semctl();
    原型:int semctl(int semid,int semnum,int cmd,union semunarg);
    返回值:如果成功,则为一个正数。
    如果失败,则为-1:errno=EACCESS(权限不够)
    EFAULT(arg指向的地址无效)
    EIDRM(信号量集已经删除)
    EINVAL(信号量集不存在,或者semid无效)
    EPERM(EUID没有cmd的权利)
    ERANGE(信号量值超出范围)
        系统调用semctl用来执行在信号量集上的控制操作。这和在消息队列中的系统调用msgctl是十分相似的。但这两个系统调用的参数略有不同。因为信号 量一般是作为一个信号量集使用的,而不是一个单独的信号量。所以在信号量集的操作中,不但要知道IPC关键字值,也要知道信号量集中的具体的信号量。这两 个系统调用都使用了参数cmd,它用来指出要操作的具体命令。两个系统调用中的最后一个参数也不一样。在系统调用msgctl中,最后一个参数是指向内核 中使用的数据结构的指针。我们使用此数据结构来取得有关消息队列的一些信息,以及设置或者改变队列的存取权限和使用者。但在信号量中支持额外的可选的命 令,这样就要求有一个更为复杂的数据结构。
    系统调用semctl()的第一个参数是关键字值。第二个参数是信号量数目。
        参数cmd中可以使用的命令如下:
        ·IPC_STAT读取一个信号量集的数据结构semid_ds,并将其存储在semun中的buf参数中。
        ·IPC_SET设置信号量集的数据结构semid_ds中的元素ipc_perm,其值取自semun中的buf参数。
        ·IPC_RMID将信号量集从内存中删除。
        ·GETALL用于读取信号量集中的所有信号量的值。
        ·GETNCNT返回正在等待资源的进程数目。
        ·GETPID返回最后一个执行semop操作的进程的PID。
        ·GETVAL返回信号量集中的一个单个的信号量的值。
        ·GETZCNT返回这在等待完全空闲的资源的进程数目。
        ·SETALL设置信号量集中的所有的信号量的值。
        ·SETVAL设置信号量集中的一个单独的信号量的值。
        参数arg代表一个semun的实例。semun是在linux/sem.h中定义的:
    /*arg for semctl systemcalls.*/
    unionsemun{
    intval;/*value for SETVAL*/
    structsemid_ds*buf;/*buffer for IPC_STAT&IPC_SET*/
    ushort*array;/*array for GETALL&SETALL*/
    structseminfo*__buf;/*buffer for IPC_INFO*/
    void*__pad;
        val当执行SETVAL命令时使用。buf在IPC_STAT/IPC_SET命令中使用。代表了内核中使用的信号量的数据结构。array在使用GETALL/SETALL命令时使用的指针。
        下面的程序返回信号量的值。当使用GETVAL命令时,调用中的最后一个参数被忽略:
    intget_sem_val(intsid,intsemnum)
    {
    return(semctl(sid,semnum,GETVAL,0));
    }
        下面是一个实际应用的例子:
    #defineMAX_PRINTERS5
    printer_usage()
    {
    int x;
    for(x=0;x<MAX_PRINTERS;x++)
    printf("Printer%d:%d\n\r",x,get_sem_val(sid,x));
    }
        下面的程序可以用来初始化一个新的信号量值:
    void init_semaphore(int sid,int semnum,int initval)
    {
    union semunsemopts;
    semopts.val=initval;
    semctl(sid,semnum,SETVAL,semopts);
    }
        注意系统调用semctl中的最后一个参数是一个联合类型的副本,而不是一个指向联合类型的指针。
    展开全文
  • linux中的信号量详解

    千次阅读 2011-09-28 20:57:43
    linux中的信号量详解 原文地址: http://www.diybl.com/course/6_system/linux/Linuxjs/2008627/128768.html  Linux支持的信号列表如下。很多信号是与机器的体系结构相关的,首
  • linux IPC --- 有名信号量详解

    千次阅读 2017-08-18 21:16:10
    在之前的博客中linux信号量—互斥与同步谈到无名信号量。无名信号量主要用于线程间的通信,保存在内存中,如果想要在进程间同步就必须把无名信号量放在进程间的共享内存中。而在进程间的通信中同步用的通常是有名...
  • POSIX信号量详解
  • Linux信号系统详解

    2016-10-30 20:48:33
    [Linux信号系统简介]  在Linux系统中,信号机制是在软件层次上对中断机制的一种模拟。一个进程接收到信号之后,有相应的信号的处理程序,而一个进程也可以给另外一个(或一组)进程发送信号。在内核版本的0.11...
  • 首先我们要理清一个概念:linux有两组接口函数用于信号量,一组是取自POSIX的实时扩展,用于线程;另一组称为系统V信号量,常用于进程的同步,也是本文所要解释的。这两组接口函数虽然很相近,但并不能保证它们之间...
  • Linux信号量

    千次阅读 2021-02-15 22:08:52
    查看信号量 [root@localhost ~]# kill -l 1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP 6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1 11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) ...
  • 本篇文章主要是介绍了Linux多线程使用信号量同步,详细讲诉了信号量的接口和使用,有需要的朋友可以了解一下。
  • 摘要: 总结了信号量的机制,以及各个信号量操作的函数,最后通过公示栏问题,将信号量机制引入加深了理解。 一、什么是信号量  信号量的主要用途是保护临界资源,进程根据信号量用于判断能否访问某些共享...
  • 只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。下面我们逐个介绍和信号...
  • Linux 信号量

    千次阅读 2017-08-19 18:20:49
    信号量信号量主要用于进程和线程间的同步,信号量保存一个整数值来控制对资源的访问,当值大于0时,表示资源空闲可以访问,等于0时表示资源分配完毕无法访问,小于0时表示有至少1个线程(进程)正在等待资源。...
  • 主要介绍了详解Linux进程间通信——使用信号量,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • IPC之信号量详解

    2017-03-06 11:40:02
    一、信号量介绍 作用:实现进程间的同步和互斥。 信号量,确切地说是信号量集,集合里有≥1个信号量成员。可以把信号量集比作数组,信号量当做数组用的元素。 但是,常用的信号量集都只有一个信号量成员。所以,平常...
  • 函数原型:void (*signal(int signo, void (*handle)(int))) (int)

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 19,454
精华内容 7,781
关键字:

linux信号量详解

linux 订阅