logo资料库

Linux之信号量_很全面的分析_个人整理的.pdf

第1页 / 共16页
第2页 / 共16页
第3页 / 共16页
第4页 / 共16页
第5页 / 共16页
第6页 / 共16页
第7页 / 共16页
第8页 / 共16页
资料共16页,剩余部分请下载后查看
一.什么是信号量
二.信号量的分类
三.内核信号量
1.内核信号量的构成
2.内核信号量的相关函数
4.内核信号量的使用例程
四.POSIX 信号量与SYSTEM V信号量的比较
五.POSIX信号量详解
1.无名信号量
(a)无名信号量在多线程间的同步
(b)无名信号量在相关进程间的同步
2.有名信号量
(a)有名信号量能在进程间共享的原因
(b)有名信号量相关函数说明
(c)有名信号量在无相关进程间的同步
六.SYSTEM V信号量
1.信号量结构体
2.常见的SYSTEM V信号量函数
(a)关键字和描述符
(b)创建和打开信号量
(c)关键字的获取
(d)设置信号量的值(PV操作)
(e)对信号集实行控制操作(semval的赋值等)
(f)例码
信号量 一.什么是信号量 信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程) 所拥有。 信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为 0,说明 它被占用,测试的线程要进入睡眠队列中,等待被唤醒。 二.信号量的分类 在学习信号量之前,我们必须先知道——Linux 提供两种信号量: (1) 内核信号量,由内核控制路径使用 (2) 用 户 态 进 程 使 用 的 信 号 量 , 这 种 信 号 量 又 分 为 POSIX 信 号 量 和 SYSTEM V 信号量。 POSIX 信号量又分为有名信号量和无名信号量。 有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名 信号量,其值保存在内存中。 倘若对信号量没有以上的全面认识的话,你就会很快发现自己在信号量的森林里迷 失了方向。 三.内核信号量 1.内核信号量的构成 内核信号量类似于自旋锁,因为当锁关闭着时,它不允许内核控制路径继续进行。然而, 当内核控制路径试图获取内核信号量锁保护的忙资源时,相应的进程就被挂起。只有在资源 被释放时,进程才再次变为可运行。 只有可以睡眠的函数才能获取内核信号量;中断处理程序和可延迟函数都不能使用内 内核信号量是 struct semaphore 类型的对象,它在中定义: 核信号量。 struct semaphore {    atomic_t count;    int sleepers;    wait_queue_head_t wait;   }
count:相当于信号量的值,大于 0,资源空闲;等于 0,资源忙,但没有进程等待这 个保护的资源;小于 0,资源不可用,并至少有一个进程等待资源。 wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。 sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。 2.内核信号量的相关函数 (1)初始化: 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,表示资 源忙 (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.内核信号量的使用例程 在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的 共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。Linux 内核中 解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用)。 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); }
四.POSIX 信号量与 SYSTEM V 信号量的比较 1. 对 POSIX 来说,信号量是个非负整数。常用于线程间同步。 而 SYSTEM V 信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体, 这个结构体是为 SYSTEM V IPC 服务的,信号量只不过是它的一部分。常用于进程间同步。 2.POSIX 信号量的引用头文件是“”,而 SYSTEM V 信号量的引用头 文件是“”。 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 中间所形成的 临界区内,这样该变量就会被保护起来,例如: #include #include #include #include #include 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 #include #include #include #include #include #include #include #include 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); } 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 #include #include #include #include #include #include #include #include #define SHMSZ 27 char SEM_NAME[]= "vik"; int main() { char ch; int shmid; key_t key;
分享到:
收藏