`
helloyesyes
  • 浏览: 1327269 次
  • 性别: Icon_minigender_2
  • 来自: 武汉
文章分类
社区版块
存档分类
最新评论

Kernel module编程(十三):信号量、互斥锁、读写信号量和完成量

阅读更多

本文也即《Linux Device Drivers》,LDD3的第五章Concurrency and Race Conditions的读书笔记之二,但我们不限于此内容。

信号量(Semaphore)

信号量和互斥锁

   Kernel提供不同的原语来处理不同的情况,最常用的是采用信号量的方式。如果不能获得资源将进入sleep状态,等待资源释放,也即block的方 式。通过加锁的原语使之sleep,例如在scull的write()的例子中,kmalloc很适合,但是不是所有情况都适合sleep的方式。信号量 是一种sleep机制。它包含一个整数,以及一对函数P和V。进程如果需要进入将调用P,如果信号量的值大于0,那么该值减一,继续执行,如果信号量等于 或者小于0,进程将等待其他人释放型号了。Unlock信号量即调用V,它将信号量的值加1,如果可能唤醒正在等待的进程。

   如果信号量用户互斥(mutex:mutual exclusion),将信号量的值初始化的1,这样只允许一个进程或者线程执行。这种情况下,信号量也成为互斥锁。在linux kernel中基本上是由于的信号量都是互斥锁。

   在scull中,我们已经以互斥锁的方式使用过信号量,在scull_write()中有竞争导致的内存泄漏的危险:

if(!dptr->data[s_pos]){
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if(dptr->data[s_pos] == NULL)
goto out;
… …
}

   这里分配了内存空间kmalloc,然后进行copy内容,如果在执行的过程中,有多个进程同时触发了scull_write,就发生竞争,例如A执行第 一句判断,当尚未执行第二句kmalloc时,B执行第一句,因此B也需要执行第二句,因此两个进程对data[s_pos]都需要kmalloc空间, 并执行copy内容,导致内容混乱是一个方面,A分配的空间,他的指针消失了,我们无法对A的kmalloc进行释放,它将占据内存的一个空间,直至系统 关闭,这就是内存泄漏。

   信号量的使用例子见kernel module编程(五):设备读写 ,相关操作已经重点标出,这里不再重复。使用方式如下:

  1. 头文件加载:#include <asm/semaphore.h>, 相关的数据结构为struct semaphore
  2. 创建信号量:void sema_init(struct semaphore * sem , int val ); 当val设置为1时就成为互斥锁。对于互斥锁,也可以采用如下的方式:
    1. 声明和初始化一个互斥锁:DECLARE_MUTEX( name ) 或者DECLARE_MUTEX_LOCKED( name ), 其中name是一个struct semaphore的变量。 在后一种方式,起始状态就是locked,其他线程/进程需要等待其unlocked才能进入。
    2. 如果一个互斥锁需要实时初始化,即动态产生,使用:void int_MUTEX(struct semaphore * sem ) ;或者void init_MUTEX_LOCKED(struct semaphore * sem );
  3. 对于信号量获取,即P函数,通过down来调用,对信号量进行减一操作,如果信号量不满足要求,则将调用者置为sleep状态直至资源获取。方式如下:
    1. void down (struct semaphore * sem); 对信号量减一,并一直等到资源获取。
    2. int down_interruptible (struct semaphore *sem); 同上,但是采用中断的方式(interruptible),这是最为常用的使用方式,它允许用户中断一个正在等待信号量获取的用户空间的进程。如果采用非 中断方式,则这个这个期间,进程是无法killed的。我们需要注意的是,采用这种方式,如果在等待期间用户终止了进程,则将返回一个非零的值,所以必须 判断返回值,并作出相应处理。一般返回为-ERESTARTSYS ,在返回之前,应当确保undo之前用户可察觉的所有操作,如果不能确保,返回-EINTR .
    3. int down_trylock(struct semaphore * sem ); 这种方式不会sleep,如果不能成功会的信号量,将马上返回非零值。
  4. 当一个线程成功调用down,即获得的semahore,将进入这段敏感代码区,执行完后,必须释放信号量。通过up来调用V操作。void up(struct semaphre * sem) 。我们要非常注意,如果在处理过程中出现异常或者错误而需要return,必须要保证信号量的释放,即无论是正常的还是异常地离开这段敏感代码区,都必须释放信号量 。否则任何线程/进程将无法获取信号量。

   我们需要注意,一定要在获取信号量之前确保已经初始化。在语句先后执行顺序中必须要优先处理。

读写信号量

  scull 的例子中,读和写都通过信号量进行保护,防止一起写,也防止在写和读同时操作,这些都是我们应当避免的,当时它同时也不允许两个读的操作一通进行,而这种 情况是不会产生危害的。Linux kernel提供了一个rwsem的特别的信号量用于处理这种情况,允许多个读同时存在以提高程序处理能力。读写信号量在驱动中一般较少使用,但是有时会 非常有效。使用方式如下:

  • 头文件加载:#include <linux/rwsem.h> ,相应的的数据结构为struct rw_semaphore
  • 初始化操作:void init_rw_sem(struct rw_semaphore * sem );
  • 对于只读操作,相关函数如下:
    • void down_read(struct rw_semaphore * sem ); 提供只读获取的保护,可以同时有其他的只读操作。他将置调用者与非中断的sleep,这是需要特别注意的,即如果另外有写的操作,而引起sleep,是非中断,用户不能在此刻中断进程。
    • int down_read_trylock(struct rw_semaphore * sem ); 马上返回,如果可读,返回非零,不可读,返回0。注意这个返回和一般的kernel函数的方式方式不一样。也可信号量的返回方式不一样。
    • void up_read(struct rw_semaphore * sem ); 释放读写信号量。
  • 对于写的保护,相关函数如下:
    • void down_write(struct rw_semaphore * sem ); 类似down_read
    • int down_write_trylock(struct rw_semaphore * sem ); 类似down_read_trylock
    • void up_write(struct rw_semaphore * sem ); 类似up_read
    • void downgrade_write(struct rw_semaphore * sem ); 当一个写保护后,跟着一个耗费时间长的读保护,我们可以在使用能够downgrade_write,它允许其他读操作在你结束写后,马上获得读写型号了。否侧通常的处理是需要等待这个紧跟的漫长的读操作。

   读写信号量允许一个写用户或者无限个读用户来获得,写用户将具备优先具备,当一个写试图进入这段关键操作代码时,其他读者都无法获得信号量,必须等待所有的写完成。因此它适合于写操作比较少,且写的过程比较短的情况,不适合存在大量写,这会阻碍读的处理。

completion(不知道中文名字,可能是完成量,^_^)

   一般信号量的的处理会限制在一个函数内,但是有时会函数A的处理的前提条件是函数B,A必须等待B处理后才能继续,可以用信号量来进行处理,但linux kernel提供complete的方式。使用方式如下:

  • 头文件#include ,数据结构为struct completion ,初始化为init_completion(struct completion * comp ) ,也可以直接使用DECLARE_COMPLETION( comp );
  • 在A函数中,如果需要等待其他的处理,使用void wait_for_completion(struct completion * comp ); 则在这个位置上将处于非中断的sleep,进行等待,也就是相关的线程/进程,用户是无法kill的。
  • 在B函数,如果已经处理完,可以交由A函数处理,有下面两种方式
    • void complete(struct completion * comp ); 如果要执行A必须等待B先执行,B执行后,A可以继续执行。如果A需要再次执行,则需要确保下一次B执行完。如果连续执行两次B,则可以执行两次A,第三次A要等第三次B执行完。
    • void complete_all(struct completion * comp ); 只要B执行完,A就可以执行,无论执行多少次。如果需要再等待B的直系个可以使用INIT_COMPLETION(struct completion * comp ) 。重新初始化completion即可。
    • void complete_and_exit(struct completion * comp ,long retval ) ; 这个处理具有complete的功能外,还将调用它的线程/进程终止。可用于一些无限循环的场景,例如受到某个cleaned up的信息后,e通知用户程序终止,允许A函数执行。

   针对我们的例子Scull,其实completion并不是合适的场景,但我们可以通过它来试验一下。我们希望是在scull的读操作之前都先完成一次写操作。

#include <linux/completion.h>
... ...
DECLARE_COMPLETION(comp); //为了试验方便,就不分每个scull都持有一个完成量,本来应当如此

int scull_read(... ...){
struct scull_qset * dptr;
... ...

printk("scull_read waiting for completed from write function.\n");
wait_for_completion (&comp);
printk("scull_read awake for reading,continue ....\n");
... ....
}

int scull_write(... ...){
... ...

complete (&comp);
// complete_all (&comp);
// complete_and_exit (&comp);
}

   对于scull0,我们原来在用户空间有一个读写测试例子,将其分为读测试和写测试。当我们调用读测试是,例子sleep,只有调用写测试时,读测试才能 继续进行。但是发现,scull_read的使用发现scull的内核模块发生crash,这是因为在wait_for_completion()之前, 对一些变量进行赋值,例如dptr,而这些变量在write的时候是发现改变的,因此出现错误,需要在wait_for_completion后面对这些 变量进行赋值。这样解决了crash的问题。但是我们发现在写测试后,读测试可以继续进行,但是很快又陷入了等待状态。下面是读测试的有关代码:

file = fopen("dev/scull0","r");
... ...

while((len = fread(read_buf,1,512,file)) > 0){
t otal_len +=len;
printf("%s",read_buf);
memset(read_buf,0,512);
}

   会调用scull_read直至scull_read返回0或者<0为止。因为已经进行了写操作,所以第一次调用返回内容长度,会出现第二次调用。 这样会将程序搞得很混乱,实际上我们写测试,因为写的内容少,可以一次写完,也很可能分为多次写。所以在这里加完成量是不合适的,可以在fopen中进行 处理,即scull_open中进行处理,例如:

int scull_open(....)
{
... ...
if((filp->f_flags & O_ACCMODE) == O_RDONLY){
printk("waiting for completed from write function.\n");

wait_for_completion(&comp);
printk("awake for reading,continue ....\n");
}
... ...
}

   这是更为合理的方式。如果complete的位置仍然放在scull_write中,我们试验三种方式。complete_and_exit(),可以要 求写测试中写入大量的内容,我们将发现只写了部分的内容(第一次调用scull_write) ,测试程序就退出。scull并不是个合适的completion的例子,completion可能会引起这样或者那样的问题,需要仔细规划。

分享到:
评论

相关推荐

    信号量与互斥锁

    在现代操作系统与多线程编程中,信号量(Semaphore)与互斥锁(Mutex)是两种广泛使用的关键同步机制,它们的设计旨在解决多线程环境下的资源竞争问题,确保数据的一致性和程序的正确运行。本文将深入探讨信号量与...

    信号量、互斥体和自旋锁的区别

    **信号量**、**互斥体**和**自旋锁**是操作系统中三种常用的同步机制,主要用于解决多线程或多进程环境中资源的并发访问问题。这三种机制虽然都用于实现同步控制,但在具体的应用场景和技术特性上存在显著差异。 ##...

    基于Linux的实现进程的信号量互斥申请

    本项目"基于Linux的实现进程的信号量互斥申请"聚焦于如何在Linux操作系统环境下,利用信号量来实现进程间的互斥访问,确保关键资源的安全共享。 信号量是一种特殊的变量,用于控制多个进程对共享资源的访问。在...

    互斥锁、条件变量、信号量总结

    互斥锁、条件变量和信号量是操作系统中用于线程同步和资源管理的重要工具,尤其在多线程和多进程编程中发挥着关键作用。这些机制确保了共享资源的有序访问,防止数据竞争和死锁等问题的发生。 首先,互斥锁(Mutex...

    基于consul的分布式锁工具,包含:互斥锁、信号量等工具

    本项目利用Consul的KV存储功能,构建了一套Java实现的分布式锁工具,包括互斥锁和信号量,旨在简化在分布式环境中的同步操作。 1. **Consul简介** Consul是由HashiCorp公司开发的一款服务发现与配置工具,它具备...

    3.线程间同步和通信之互斥锁(静态)

    在STM32平台上,RT-thread提供了一套完善的线程管理、信号量和互斥锁等同步机制。RT-thread中的互斥锁API包括`rt_mutex_init`、`rt_mutex_take`、`rt_mutex_release`和`rt_mutex_destroy`等函数。 1. `rt_mutex_...

    进程编程 消息机制 信号量 互斥

    本文将深入探讨“进程编程”、“进程通讯”、“消息机制”、“信号量”以及“互斥”这五个核心概念。 首先,我们要理解什么是进程。进程是操作系统中的一个基本实体,代表了一个正在执行的程序的实例。每个进程都有...

    8. 递归互斥信号量.zip

    二进制信号量只有两个状态:被占用或未被占用,类似于互斥锁。计数信号量则可以有大于零的计数值,用于允许多个任务同时访问共享资源。递归互斥信号量结合了这两种信号量的特点,它具有计数功能,但又限制了只能由...

    原子操作、信号量、读写信号量和自旋锁的API

    ### 原子操作、信号量、读写信号量和自旋锁的API详解 #### 一、引言 在现代操作系统中,特别是在多处理器环境下,确保数据的一致性和完整性至关重要。为此,Linux内核提供了多种同步机制来保护共享资源免受并发...

    【freertos】011-信号量、互斥量及优先级继承机制源码分析.doc

    信号量的数据结构包括信号量的值、阻塞链表、信号量内部锁等,而互斥量的数据结构包括互斥量的值、阻塞链表、互斥量内部锁等。函数调用包括信号量的创建、获取、释放和删除等。 在FreeRTOS中,信号量和互斥量的使用...

    STM32F429 FreeRTOS实战:实现FreeRTOS互斥信号量操作【支持STM32F42X系列单片机】.zip

    FreeRTOS是为嵌入式系统设计的开源RTOS,它提供了调度、任务、信号量、互斥锁、消息队列等多种功能,帮助开发者构建复杂的实时系统。STM32F429是STMicroelectronics公司推出的高性能ARM Cortex-M4内核MCU,具备浮点...

    stm32f103 FreeRTOS互斥信号量操作实验.zip

    STM32F103与FreeRTOS互斥信号量操作实验是嵌入式开发中的一个典型应用场景,这个实验旨在帮助初学者和有一定基础的单片机开发者更好地理解和应用实时操作系统FreeRTOS,以提高代码开发效率和编程技能。FreeRTOS是一...

    linux内核知识系列:同步与互斥

    在Linux内核中,常见的同步机制包括信号量(semaphore)、自旋锁(spinlock)、读写锁(rwlock)、完成标志(completion)等。 1. **信号量(Semaphore)**: 信号量是一种经典的同步原语,分为整型信号量和记录型...

    使用信号量和关键段实现多线程的同步与互斥

    这个问题通常通过信号量解决,可以分为"一个读者一个写者"、"多个读者一个写者"和"多个读者多个写者"三种情况。 4. **读者优先策略(Reader Priority)** 在这个项目中,可能会实现读者优先策略,即当没有写者时,...

    《临界区,互斥,信号量》

    临界区、互斥量和信号量都是用于线程间同步的重要工具。临界区提供了一种轻量级的锁定机制,适用于同一进程内线程之间的同步;互斥量支持更广泛的场景,包括跨进程的线程同步;而信号量则提供了一种更灵活的方式来...

    linux和win32下通用的互斥锁Mutex封装

    互斥锁是一种二状态信号量,状态为锁定或未锁定。当一个线程获得锁后,其他试图获取该锁的线程将被阻塞,直到锁被释放。这样就保证了对共享资源的独占访问,避免了数据竞争问题。在Linux中,互斥锁通常通过pthread_...

    Python多线程编程(四):使用Lock互斥锁

    除了互斥锁之外,Python的`threading`模块还提供了其他同步机制,如信号量(Semaphore)、事件(Event)和条件变量(Condition),它们在不同场景下各有优势,可以根据具体需求选择合适的同步工具。 总之,Python的...

    QT 下 信号量使用

    二进制信号量和互斥锁的区别** 二进制信号量是QSemaphore的一个特例,它的值只能是0或1。在QT中,QMutex也可以实现类似的功能,但QSemaphore更灵活,可以用于控制同时访问的线程数量。 **4. 防止死锁** 在使用...

    使用共享内存及信号量实现进程间通信例子

    在Linux中,信号量有两种类型:互斥量(互斥锁)和计数信号量。互斥量只允许一个进程访问资源,而计数信号量可以控制同时访问资源的进程数量。`semget`用于创建信号量集,`semop`执行信号量操作(如P(等待)和V...

    Linux内核源码深度解析与开发实战视频.zip

    网盘文件永久链接 ...20:可睡眠锁:读写信号量rwsem_rec 21:可睡眠锁:完成变量completion_rec 22:可睡眠锁:SRCUsleepable_read-copy-update_rec 23:原子操作_rec 24:内存屏障_rec ...........

Global site tag (gtag.js) - Google Analytics