信号量学习笔记

12 篇文章 0 订阅

信号量的作用:

                           保证同一时间,某一资源只被一个进程访问。

信号量的取值:

                         if (信号量==0)

                           {

                              不能访问资源;

                           }  

                        else if(信号量》=0)

                          {

                            可以访问资源;

                          }

                        else

                         {

                          我也不知道啊;

                          }

信号量的分类:

                      信号量======(1)内核信号量

                                 =

                                 =

                                 ======(2)用户信号量=====(1)SYSTE V信号量

                                                                         =

                                                                         =

                                                                         =====(2)POSIX信号量=====(1)有名信号量

                                                                                                                   =

                                                                                                                   =

                                                                                                                   =====(2)无名信号量

内核信号量:

(1)什么样的进程可以使用信号量?

        A:可以睡眠的进程,可以访问信号量。中断程序和可延迟函数不能使用信号量。

(2)什么是信号量:

         用一个结构对信号量进行抽象:

         struct semaphore {                                        atomic_t count;                                        int sleepers;                                        wait_queue_head_t wait;                                       };

         count:信号量的值

         sleepers:是否有一些进程在信号量上睡眠

         wait:实际是一个链表,所有等待访问该资源的睡眠进程的队列

(3)信号量的相关函数:

          1)信号量的初始化函数:

             void sema_init(struct semaphore*sem ,int val);

             解析:给信号量一个初值

             void init_MUTEX (struct semaphore *sem); 

             解析:将 sem的count的值置为 1,表示资源空闲             void init_MUTEX_LOCKED (struct semaphore *sem);

             解析:将 sem的count的值置为 0,表示资源忙

         2)申请信号量所保护的资源:

            void down(struct semaphore * sem);            解析:可引起睡眠           int down_interruptible(struct semaphore * sem); 

            解析:down_interruptible 能被信号打断

           int down_trylock(struct semaphore * sem);            解析: 非阻塞函数,不会睡眠。无法锁定资源则马上返回

        3)释放内核信号量所保护的资源:           void up(struct semaphore * sem);

(4):内核信号量使用的实例:

          1)内核信号量能解决那些问题:

               A:在驱动程序中,当多个线程同时访问相同的资源时

                   (驱动中的全局变量时一种典型的Linux 内核中共享资源)

                   可能会引发“竞态“,因此我们必须对共享资源进行并发控制。                   解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。

         2)应用实例:

            

ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
 //获得信号量
 if (down_interruptible(&sem))
 {
  return - ERESTARTSYS;
 }
 //将用户空间的数据复制到内核空间的 global_var
 if (copy_from_user(&global_var, buf, sizeof(int)))
 {
  up(&sem);
  return - EFAULT;
 }
 //释放信号量
 up(&sem);
 return sizeof(int);


用户信号量:

(1)对比SYSTEM V和POSIX 信号量

        1. 对 POSIX 来说,信号量是个非负整数。常用于线程间同步。
            而 SYSTEM V 信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,
            这个结构体是为 SYSTEM V IPC 服务的,信号量只不过是它的一部分,常用于进程间同步。
        2.POSIX 信号量的引用头文件是“<semaphore.h>”

           SYSTEM V 信号量的引用头文件是“<sys/sem.h>”。
        3.从使用的角度,System V 信号量是复杂的,而 Posix 信号量是简单。

           比如,POSIX 信号量的创建和初始化或 PV 操作就很非常方便。


(2)POSIX信号量


         1)无名信号量


              无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。

              然后再初始化该无名信号量,之后就可以放心使用了。

 
              无名信号量常用于多线程间的同步,同时也用于相关进程间的同步


              也就是说,无名信号量必须是多个进程(线程)的共享变量,

              无名信号量要保护的变量也必须是多个进程(线程)的共享变量,这两个条件是缺一不可的。

           

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

               

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 中间所形成的
               临界区内,这样该变量就会被保护起来,例如:

#include <pthread.h>
#include <semaphore.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int number; // 被保护的全局变量
sem_t sem_id;
void* thread_one_fun(void *arg)
{
sem_wait(&sem_id);
printf("thread_one have the semaphore\n");
number++;
printf("number = %d\n",number);
sem_post(&sem_id);
}
void* thread_two_fun(void *arg)
{
sem_wait(&sem_id);
printf("thread_two have the semaphore \n");
number--;
printf("number = %d\n",number);
sem_post(&sem_id);
}
int main(int argc,char *argv[])
{
number = 1;
pthread_t id1, id2;
sem_init(&sem_id, 0, 1);
pthread_create(&id1,NULL,thread_one_fun, NULL);
pthread_create(&id2,NULL,thread_two_fun, NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
printf("main,,,\n");
return 0;
}

              上面的例程,到底哪个线程先申请到信号量资源,这是随机的。 
              如果想要某个特定的顺序的话,可以用 2 个信号量来实现。

              例如下面的例程是线程 1 先执行完,然后线程 2 才继续执行,直至结束。

int number; // 被保护的全局变量
sem_t sem_id1, sem_id2;
void* thread_one_fun(void *arg)
{
sem_wait(&sem_id1);
printf("thread_one have the semaphore\n");
number++;
printf("number = %d\n",number);
sem_post(&sem_id2);
}
void* thread_two_fun(void *arg)
{
sem_wait(&sem_id2);
printf("thread_two have the semaphore \n");
number--;
printf("number = %d\n",number);
sem_post(&sem_id1);
}
int main(int argc,char *argv[])
{
number = 1;
pthread_t id1, id2;
sem_init(&sem_id1, 0, 1); // 空闲的
sem_init(&sem_id2, 0, 0); // 忙的
pthread_create(&id1,NULL,thread_one_fun, NULL);
pthread_create(&id2,NULL,thread_two_fun, NULL);
pthread_join(id1,NULL);
pthread_join(id2,NULL);
printf("main,,,\n");
return 0;
}

          b)无名信号量在进程间的同步:

                

               说是相关进程,是因为本程序中共有 2 个进程,其中一个是另外一个的子进程(由fork产生)的。
               本来对于 fork 来说,子进程只继承了父进程的代码副本,mutex 理应在父子进程
               中是相互独立的两个变量,但由于在初始化 mutex 的时候,由 pshared = 1
               指定了 mutex 处于共享内存区域,所以此时 mutex 变成了父子进程共享的一个变量。

               此时,mutex 就可以用来同步相关进程了。

              

#include <semaphore.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char **argv)
{
int fd, i,count=0,nloop=10,zero=0,*ptr;
sem_t mutex;
//open a file and map it into memory
fd = open("log.txt",O_RDWR|O_CREAT,S_IRWXU);
write(fd,&zero,sizeof(int));
ptr = mmap( NULL,sizeof(int),PROT_READ |
PROT_WRITE,MAP_SHARED,fd,0 );
close(fd);
/* create, initialize semaphore */
if( sem_init(&mutex,1,1) < 0) //
{
perror("semaphore initilization");
exit(0);
}
if (fork() == 0)
{ /* child process*/
for (i = 0; i < nloop; i++)
{
sem_wait(&mutex);
printf("child: %d\n", (*ptr)++);
sem_post(&mutex);
}
exit(0);
}
/* back to parent process */
for (i = 0; i < nloop; i++)
{
sem_wait(&mutex);
printf("parent: %d\n", (*ptr)++);
sem_post(&mutex);
}
exit(0);
}

         fork()创建进程的相关知识:http://baike.baidu.com/view/1952900.htm

        

             2)有名信号量:

                             

                   什么是有名信号量:

                           有名信号量的特点是把信号量的值保存在文件中。
                           这决定了它的用途非常广:既可以用于线程,也可以用于相关进程间,甚至是不相关进程。

                  

                  a)有名信号量能在进程中共享的原因

                         由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父
                         进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,

                         当然文件里面保存的有名信号量值就共享了。

                  b)有名信号量的相关函数

                        有名信号量在使用的时候,和无名信号量共享 sem_wait 和 sem_post 函数。
                        区别是有名信号量使用 sem_open 代替 sem_init,另外在结束的时候要像关闭文件
                        一样去关闭这个有名信号量

                 

                 打开一个已存在的有名信号量,或创建并初始化一个有名信号量。

                 一个单一的调用就完成了信号量的创建、初始化和权限的设置。

     

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。


 一旦你使用了一信号量,销毁它们就变得很重要。
在做这个之前,要确定所有对这个有名信号量的引用都已经通过 sem_close()函数
关闭了,然后只需在退出或是退出处理函数中调用 sem_unlink()去删除系统中的信号量,
注意如果有任何的处理器或是线程引用这个信号量,sem_unlink()函数不会起到任何的作用。
也就是说,必须是最后一个使用该信号量的进程来执行 sem_unlick 才有效。
因为每个信号灯有一个引用计数器记录当前的打开次数,sem_unlink 必须等待这个数为 0 时才能
把 name 所指的信号灯从文件系统中删除。也就是要等待最后一个 sem_close 发生。


                 c)有名信号量在无相关进程间的同步

                    前面已经说过,有名信号量是位于共享内存区的,那么它要保护的资源也必须是

                    位于共享内存区,只有这样才能被无相关的进程所共享。
                    在下面这个例子中,服务进程和客户进程都使用 shmget 和 shmat 来获取得一块共

                    享内存资源。然后利用有名信号量来对这块共享内存资源进行互斥保护



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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值