首页 > 范文大全 > 正文

信号量在Linux多线程机制中的应用

开篇:润墨网以专业的文秘视角,为您筛选了一篇信号量在Linux多线程机制中的应用范文,如需获取更多写作素材,在线客服老师一对一协助。欢迎您的阅读与分享!

【摘 要】本文以信号量原理为基础,重点阐述信号量在linux多线程同步机制中的实现特色。

【关键词】信号量;Linux;多线程;同步

1 信号量

1965年E. W. Dijkstra首次提出信号量的概念,用于解决进程间同步或互斥的多方协调运行问题,因其工作时交换信息量较少,故将其归类为进程的低级通信方式,有别于管道通信、消息传递等能传输大量数据的高级通信方式。操作系统引入多线程机制后,信号量也广泛应用于多线程的同步与协调,避免多个线程同时进入临界区而造成的运行结果不确定。

通常信号量使用一个整型变量表示,进程(或是线程)只能对其实施两个操作:P操作和V操作,前者可使信号量的值减1,后者则对信号量值加1。进程间若存在有直接制约(逻辑次序关系)关系,则执行顺序在前的进程执行完毕需使用V操作发出信号,而顺序在后的进程执行前需使用P操作检查信号。若进程间存在的是间接制约(竞争临界资源)关系,则使用资源之前需执行P操作检查资源状态是否可用,即申请资源,若P操作成功,则执行临界区,执行完毕退出临界区,使用V操作释放资源。此处,P操作会面临失败的可能,那么对应执行的进程会进入与信号量挂钩的一个等待队列,即转入等待状态,直到其他进程通过V操作释放资源,从而唤醒等待进程继续执行。

2 Linux系统下的多线程

Linux线程属于核心级线程,但其实现方式比较特别,每个线程对内核来说都是一个进程。LinuxThreads是目前Linux平台上使用最为广泛的线程库,采用基于内核支持轻量级进程支持的线程一对一模型,即每个线程依赖于一个内核轻量级进程的模式,由核心完成线程调度,同时在核外函数库中实现诸如线程同步互斥、线程创建取消等线程管理工作。在LinuxThreads中,每一个进程都一个对应的管理线程,负责处理线程相关的管理工作。当进程第一次调用pthread_create( )创建一个线程的时候就会调用__clone( )并启动管理线程。

Linux提供两种信号量:1)内核信号量,由内核控制路径使用;2)用户态进程使用的信号量,这种信号量又分为POSIX信号量和SYSTEM V信号量。POSIX信号量又分为有名信号量和无名信号量。有名信号量,其值保存在文件中,所以它可以用于线程也可以用于进程间的同步。无名信号量,其值保存在内存中。对POSIX来说,信号量是个非负整数,常用于线程间同步。而SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,这个结构体是为 SYSTEM V IPC服务的,信号量只不过是它的一部分,常用于进程间同步。

本文的研究将主要围绕内核信号量及无名信号量在实现多线程同步上的应用。

3 内核信号量

3.1 内核信号量的相关函数

3.1.1 初始化

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

2)void init_MUTEX(struct semaphore *sem); //将sem的值置1,表示资源空闲

3)void init_MUTEX_LOCKED(struct semaphore *sem); //将sem的值置为0,表示资源忙

3.1.2 申请内核信号量所保护的资源

1)void down(struct semaphore * sem); // 可引起睡眠

2)int down_interruptible(struct semaphore*sem); // down_interruptible能被信号打断

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

3.1.3 释放内核信号量所保护的资源

1)void up(struct semaphore * sem);

3.2 内核信号量的使用例程

在驱动程序中,当多个线程同时访问相同的资源时(驱动中的全局变量时一种典型的共享资源),可能会引发“竞态“,因此我们必须对共享资源进行并发控制。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);

}

4 无名信号量

4.1 无名信号量相关函数

无名信号量的创建就像声明一般的变量一样简单,例如:sem_t sem_id。然后再初始化该无名信号量,之后就可以放心使用了。

无名信号量常用于多线程间的同步,同时也用于相关进程间的同步。也就是说,无名信号量必须是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程(线程)的共享变量,这两个条件是缺一不可的。

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

1)int sem_init(sem_t *sem, int pshared, unsigned int value)

①pshared==0 用于同一多线程的同步;

②若 pshared>0 用于多个相关进程间的同步(即由fork产生的)。

2)int sem_getvalue(sem_t *sem, int *sval)

取回信号量 sem 的当前值,把该值保存到 sval 中。

若有 1 个或更多的线程或进程调用 sem_wait 阻塞在该信号量上,该函数返回两种值:①返回0;②返回阻塞在该信号量上的进程或线程数目。Linux 采用返回的第一种策略。

3)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。

4)sem_post 相当于V操作,释放资源

int sem_post(sem_t *sem);

把指定的信号量sem的值加1;唤醒正在等待该信号量的任意线程。

在这些函数中,只有sem_post是信号安全的函数,它是可重入函数。

4.2 无名信号量在多线程间的同步

无名信号量的常见用法是将要保护的变量放在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个信号量来实现。

5 结束语

本文以信号量原理为基础,对Linux系统中多线程同步机制中常用的信号量的相关函数进行了简单分析总结,并给出了简单例程。多线程同步机制还采用了诸如自旋锁、顺序锁、RCU等同步工具,往往相互结合使用,也是我们深入研究与内核编程的难点。本文源码分析基于Linux 2.4版本,与现有的2.6、3.X版本在信号量机制的实现方面有所不同,但主要的区别在后两者还实现了多CPU机制的支持,其它大致是一样的。