你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

Linux 自旋锁spinlock

[复制链接]
gaosmile 发布时间:2020-9-4 20:23
背景

由于在多处理器环境中某些资源的有限性,有时需要互斥访问(mutual exclusion),这时候就需要引入锁的概念,只有获取了锁的任务才能够对资源进行访问,由于多线程的核心是CPU的时间分片,所以同一时刻只能有一个任务获取到锁。

内核当发生访问资源冲突的时候,通常有两种处理方式:

  • 一个是原地等待
  • 一个是挂起当前进程,调度其他进程执行(睡眠)
    0 Z/ j# D+ }: }6 I, t& E* F* Q5 S
自旋锁

Spinlock 是内核中提供的一种比较常见的锁机制,自旋锁是“原地等待”的方式解决资源冲突的。即,一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到,只能够原地“打转”(忙等待)。

由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制 —— 自旋锁不应该被长时间的持有(消耗 CPU 资源)。

自旋锁的优点

自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减少了不必要的上下文切换,执行速度快。

非自旋锁在获取不到锁的时候会进入阻塞状态,从而进入内核态,当获取到锁的时候需要从内核态恢复,需要线程上下文切换。(线程被阻塞后便进入内核(Linux)调度状态,这个会导致系统在用户态与内核态之间来回切换,严重影响锁的性能)。

自旋锁的使用

在linux kernel的实现中,经常会遇到这样的场景:共享数据被中断上下文和进程上下文访问,该如何保护呢?

如果只有进程上下文的访问,那么可以考虑使用semaphore或者mutex的锁机制,但是现在中断上下文也掺和进来,那些可以导致睡眠的lock就不能使用了,这时候,可以考虑使用spin lock。

在中断上下文,是不允许睡眠的,所以,这里需要的是一个不会导致睡眠的锁——spinlock。

换言之,中断上下文要用锁,首选 spinlock。

使用自旋锁,有两种方式定义一个锁:

动态的:

spinlock_t lock;: Y. r5 f1 q1 Q5 i$ J1 C
spin_lock_init (&lock);2 Y, V, J$ i# U& G: n, T

静态的:

DEFINE_SPINLOCK(lock);
* F" V- X" s4 E
使用步骤

spinlock的使用很简单:

  • 我们要访问临界资源需要首先申请自旋锁;
  • 获取不到锁就自旋,如果能获得锁就进入临界区;
  • 当自旋锁释放后,自旋在这个锁的任务即可获得锁并进入临界区,退出临界区的任务必须释放自旋锁。
    - S- [, v2 W* |' L" H) g1 h

使用实例

static spinlock_t lock;
, A' f' k2 ~+ Y8 ~4 O% @5 fstatic int flage = 1;
0 q8 ^: D7 y$ C# O& d! l- w8 B% j, Z  _( |" X/ N5 e! l
spin_lock_init(&lock);+ [: F$ `7 g  H- w/ ]/ r
4 ?. f  d0 A; `) F
static int hello_open (struct inode *inode, struct file *filep)5 K/ W2 G0 R# |7 C) z) f
{
% m. f% @" _1 a4 B  spin_lock(&lock);
/ I; a+ _8 P, E# P/ X      if(flage !=1)7 A. r& f3 X5 y' [$ {; `
      {
/ c% @3 u4 T9 B, v+ L, y2 p' Q8 ?0 ]; v             spin_unlock(&lock);
2 T' `% }0 x* ^2 f. @3 C             return -EBUSY;
# {( ^9 O6 Q7 ?' B7 v6 p6 X      }- u0 v9 M" x) S
      flage =0;
' e( m( @: t) p, V$ w' p# @      spin_unlock(&lock);3 x1 W# M0 W" `

" ?& R. U% h3 {& J      return 0;$ u3 Q/ T, P' F. o0 A* f
}" c8 R3 Y# s" D2 X
static int hello_release (struct inode *inode, struct file *filep). {* A/ R1 C; Y# X2 Q; d( K/ P8 o
{% I4 E- U; H, M/ V9 g- ~
  flage = 1;
: S3 c* M1 L6 F$ R5 S  return 0;
7 b) n0 g" U: H$ a# E}
/ F% x: n3 d) G7 G; b2 T% E
补充

中断上下文不能睡眠的原因是:

  • 中断处理的时候,不应该发生进程切换,因为在中断context中,唯一能打断当前中断handler的只有更高优先级的中断,它不会被进程打断,如果在 中断context中休眠,则没有办法唤醒它,因为所有的wake_up_xxx都是针对某个进程而言的,而在中断context中,没有进程的概念,没 有一个task_struct(这点对于softirq和tasklet一样),因此真的休眠了,比如调用了会导致block的例程,内核几乎肯定会死。

  • schedule()在切换进程时,保存当前的进程上下文(CPU寄存器的值、进程的状态以及堆栈中的内容),以便以后恢复此进程运行。中断发生后,内核会先保存当前被中断的进程上下文(在调用中断处理程序后恢复);


    ( M3 C" [% K5 P- Q# ]

但在中断处理程序里,CPU寄存器的值肯定已经变化了吧(最重要的程序计数器PC、堆栈SP等),如果此时因为睡眠或阻塞操作调用了schedule(),则保存的进程上下文就不是当前的进程context了.所以不可以在中断处理程序中调用schedule()。

  • 内核中schedule()函数本身在进来的时候判断是否处于中断上下文:4 n4 ]; d9 l7 n; ?8 K' s
if(unlikely(in_interrupt()))
2 O9 z$ Q* f/ @    BUG();- F/ V7 k- r3 W8 r# Q5 K3 ?$ P( y( f

因此,强行调用schedule()的结果就是内核BUG。

  • 中断handler会使用被中断的进程内核堆栈,但不会对它有任何影响,因为handler使用完后会完全清除它使用的那部分堆栈,恢复被中断前的原貌。

  • 处于中断context时候,内核是不可抢占的。因此,如果休眠,则内核一定挂起。


    . ^( ]" k0 c; u: m8 y5 k5 L  {# O

/ q. e4 [. O* x0 A5 z

自旋锁的死锁

自旋锁不可递归,自己等待自己已经获取的锁,会导致死锁。

自旋锁可以在中断上下文中使用,但是试想一个场景:一个线程获取了一个锁,但是被中断处理程序打断,中断处理程序也获取了这个锁(但是之前已经被锁住了,无法获取到,只能自旋),中断无法退出,导致线程中后面释放锁的代码无法被执行,导致死锁。(如果确认中断中不会访问和线程中同一个锁,其实无所谓)。

一、考虑下面的场景(内核抢占场景):

(1)进程A在某个系统调用过程中访问了共享资源 R

(2)进程B在某个系统调用过程中也访问了共享资源 R

会不会造成冲突呢?

假设在A访问共享资源R的过程中发生了中断,中断唤醒了沉睡中的,优先级更高的B,在中断返回现场的时候,发生进程切换,B启动执行,并通过系统调用访问了R,如果没有锁保护,则会出现两个thread进入临界区,导致程序执行不正确。OK,我们加上spin lock看看如何:A在进入临界区之前获取了spin lock,同样的,在A访问共享资源R的过程中发生了中断,中断唤醒了沉睡中的,优先级更高的B,B在访问临界区之前仍然会试图获取spin lock,这时候由于A进程持有spin lock而导致B进程进入了永久的spin……怎么破?linux的kernel很简单,在A进程获取spin lock的时候,禁止本CPU上的抢占(上面的永久spin的场合仅仅在本CPU的进程抢占本CPU的当前进程这样的场景中发生)。如果A和B运行在不同的CPU上,那么情况会简单一些:A进程虽然持有spin lock而导致B进程进入spin状态,不过由于运行在不同的CPU上,A进程会持续执行并会很快释放spin lock,解除B进程的spin状态。

二、再考虑下面的场景(中断上下文场景):

  • 运行在CPU0上的进程A在某个系统调用过程中访问了共享资源 R
  • 运行在CPU1上的进程B在某个系统调用过程中也访问了共享资源 R
  • 外设P的中断handler中也会访问共享资源 R% U4 F7 a  Z, N" s& M- J+ r

在这样的场景下,使用spin lock可以保护访问共享资源R的临界区吗?

我们假设CPU0上的进程A持有spin lock进入临界区,这时候,外设P发生了中断事件,并且调度到了CPU1上执行,看起来没有什么问题,执行在CPU1上的handler会稍微等待一会CPU0上的进程A,等它立刻临界区就会释放spin lock的,但是,如果外设P的中断事件被调度到了CPU0上执行会怎么样?CPU0上的进程A在持有spin lock的状态下被中断上下文抢占,而抢占它的CPU0上的handler在进入临界区之前仍然会试图获取spin lock,悲剧发生了,CPU0上的P外设的中断handler永远的进入spin状态,这时候,CPU1上的进程B也不可避免在试图持有spin lock的时候失败而导致进入spin状态。为了解决这样的问题,linux kernel采用了这样的办法:如果涉及到中断上下文的访问,spin lock需要和禁止本 CPU 上的中断联合使用。

三、再考虑下面的场景(底半部场景)

linux kernel中提供了丰富的bottom half的机制,虽然同属中断上下文,不过还是稍有不同。我们可以把上面的场景简单修改一下:外设P不是中断handler中访问共享资源R,而是在的bottom half中访问。使用spin lock+禁止本地中断当然是可以达到保护共享资源的效果,但是使用牛刀来杀鸡似乎有点小题大做,这时候disable bottom half就可以了。

四、中断上下文之间的竞争

同一种中断handler之间在uni core和multi core上都不会并行执行,这是linux kernel的特性。如果不同中断handler需要使用spin lock保护共享资源,对于新的内核(不区分fast handler和slow handler),所有handler都是关闭中断的,因此使用spin lock不需要关闭中断的配合。bottom half又分成softirq和tasklet,同一种softirq会在不同的CPU上并发执行,因此如果某个驱动中的softirq的handler中会访问某个全局变量,对该全局变量是需要使用spin lock保护的,不用配合disable CPU中断或者bottom half。tasklet更简单,因为同一种tasklet不会多个CPU上并发。

自旋锁的实现原理

数据结构

首先定义一个 spinlock_t 的数据类型,其本质上是一个整数值(对该数值的操作需要保证原子性),该数值表示spin lock是否可用。初始化的时候被设定为1。当thread想要持有锁的时候调用spin_lock函数,该函数将spin lock那个整数值减去1,然后进行判断,如果等于0,表示可以获取spin lock,如果是负数,则说明其他thread的持有该锁,本thread需要spin。

内核中的spinlock_t的数据类型定义如下:

typedef struct spinlock {; N8 P. V+ a3 M+ M; v% N% m( @
   struct raw_spinlock rlock;  . D4 i$ W9 E# j: H+ d$ s
} spinlock_t;( W9 F0 w, N# C- K- x4 B. w
typedef struct raw_spinlock {
) x8 G" a, b* x2 l   arch_spinlock_t raw_lock;; z3 @; g  V' R+ ~9 h
} raw_spinlock_t;  O% ?; F. u+ C5 T) _: K

通用(适用于各种arch)的spin lock使用spinlock_t这样的type name,各种arch定义自己的struct raw_spinlock。听起来不错的主意和命名方式,直到linux realtime tree(PREEMPT_RT)提出对spinlock的挑战。

spin lock的命名规范定义如下:

  • spinlock,在rt linux(配置了PREEMPT_RT)的时候可能会被抢占(实际底层可能是使用支持PI(优先级翻转)的mutext)。
  • raw_spinlock,即便是配置了PREEMPT_RT也要顽强的spin
  • arch_spinlock,spin lock是和architecture相关的,, Y" R! B1 X; o1 z' a6 |
ARM 结构体系 arch_spin_lock 接口实现加锁

同样的,这里也只是选择一个典型的API来分析,其他的大家可以自行学习。我们选择的是 arch_spin_lock,其ARM32的代码如下:

static inline void arch_spin_lock(arch_spinlock_t *lock)$ Q3 `/ y, A0 F
{
0 C! u4 I% H2 ~. ^   unsigned long tmp;" x- Y3 {, a) B! X" d2 R* _  [
   u32 newval;/ A7 `+ Y3 M' d2 v

  S' ^3 }* @& P: Z4 K7 U   arch_spinlock_t lockval;" s4 d* N1 }: ]: ~+ o
   prefetchw(&lock->slock);---------(0)
- N9 X; P5 v5 [1 X2 i   __asm__ __volatile__(
6 |3 _: o( u6 ?: N% d2 N% c"1:    ldrex    %0, [%3]\n"---------(1)
/ q  Y$ n4 q1 F3 s8 T) x"    add    %1, %0, %4\n" ----------(2)+ ~5 M7 o0 e$ Z% e! f
"    strex    %2, %1,[%3]\n"---------(3)2 ~( d4 i9 Y7 ^
"    teq    %2, #0\n"-------------(4)7 E8 f* B$ q: K2 {
"    bne    1b") x7 P. H2 b( N0 p9 t
   : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)7 N$ F/ x# @9 ~. h5 z  F2 W9 a
   : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)
# D1 h! B, u9 |3 ?. u7 X   : "cc");
# C. b: w8 s, \3 R' W   while (lockval.tickets.next != lockval.tickets.owner) {----(5)& R0 W# w! E0 t* U( O
       wfe();------------(6)
- E5 T( E' n4 j; m: ~5 O' Z lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);----(7)
2 S/ Y9 L4 ]! s* y   }   
+ \# i" _/ ]8 h: Q+ |$ Q  n    smp_mb();-----------(8)" y6 d9 f# x9 K( E5 Q
}
" c( U$ M) k( S4 N+ v% b
(0)和preloading cache相关的操作,主要是为了性能考虑
1 J. X1 X. c3 H8 I5 m(1)lockval = lock->slock (如果lock->slock没有被其他处理器独占,则标记当前执行处理器对lock->slock地址的独占访问;否则不影响)6 N% P4 E6 f+ L% i( |
(2)newval = lockval + (1 << TICKET_SHIFT)
, t' @; K) b/ w. }0 O(3)strex tmp, newval, [&lock->slock] (如果当前执行处理器没有独占lock->slock地址的访问,不进行存储,返回1给temp;如果当前处理器已经独占lock->slock内存访问,则对内存进行写,返回0给temp,清除独占标记) lock->tickets.next = lock->tickets.next + 17 G' ]+ d" ?2 m. }( |( ?8 O1 o0 ?
(4)检查是否写入成功 lockval.tickets.next. G9 R! @! o4 L/ ~' Y6 O0 ^
(5)初始化时lock->tickets.owner、lock->tickets.next都为0,假设第一次执行arch_spin_lock,lockval = *lock,lock->tickets.next++,lockval.tickets.next 等于 lockval.tickets.owner,获取到自旋锁;自旋锁未释放,第二次执行的时候,lock->tickets.owner = 0, lock->tickets.next = 1,拷贝到lockval后,lockval.tickets.next != lockval.tickets.owner,会执行wfe等待被自旋锁释放被唤醒,自旋锁释放时会执行 lock->tickets.owner++,lockval.tickets.owner重新赋值( L- @' L' ]6 b" j9 p2 y, k  t
(6)暂时中断挂起执行。如果当前spin lock的状态是locked,那么调用wfe进入等待状态。更具体的细节请参考ARM WFI和WFE指令中的描述。
/ ^( x8 h+ q# U7 q5 U$ V* h(7)其他的CPU唤醒了本cpu的执行,说明owner发生了变化,该新的own赋给lockval,然后继续判断spin lock的状态,也就是回到step 5。, L( U# l$ b; k9 f3 J3 O) f
(8)memory barrier的操作,具体可以参考memory barrier中的描述。% S4 B  G$ q; ]1 S6 O. n: [9 ^& h
释放锁static inline void arch_spin_unlock(arch_spinlock_t *lock)
$ D. f5 v9 d2 U0 `" N' m( w{
: H$ B% w; J% t% M: X# p2 l% I smp_mb();    `3 `0 K( i3 w
lock->tickets.owner++; ---------------------- (0)
4 S8 b5 n, X- O dsb_sev(); ---------------------------------- (1)
% k0 Q' {0 L3 |, ~, j/ g4 i}
7 S5 Y) H9 v4 o4 T! j3 h
  • (0)lock->tickets.owner增加1,下一个被唤醒的处理器会检查该值是否与自己的lockval.tickets.next相等,lock->tickets.owner代表可以获取的自旋锁的处理器,lock->tickets.next你一个可以获取的自旋锁的owner;处理器获取自旋锁时,会先读取lock->tickets.next用于与lock->tickets.owner比较并且对lock->tickets.next加1,下一个处理器获取到的lock->tickets.next就与当前处理器不一致了,两个处理器都与lock->tickets.owner比较,肯定只有一个处理器会相等,自旋锁释放时时对lock->tickets.owner加1计算,因此,先申请自旋锁多处理器lock->tickets.next值更新,自然先获取到自旋锁
  • (1)执行sev指令,唤醒wfe等待的处理器4 C2 W7 U0 N# a0 T- n8 q! e/ |' O
自旋锁导致死锁实例死锁的2种情况

1)拥有自旋锁的进程A在内核态阻塞了,内核调度B进程,碰巧B进程也要获得自旋锁,此时B只能自旋转。而此时抢占已经关闭,不会调度A进程了,B永远自旋,产生死锁。

2)进程A拥有自旋锁,中断到来,CPU执行中断函数,中断处理函数,中断处理函数需要获得自旋锁,访问共享资源,此时无法获得锁,只能自旋,产生死锁。

如何避免死锁
  • 如果中断处理函数中也要获得自旋锁,那么驱动程序需要在拥有自旋锁时禁止中断;
  • 自旋锁必须在可能的最短时间内拥有;
  • 避免某个获得锁的函数调用其他同样试图获取这个锁的函数,否则代码就会死锁;不论是信号量还是自旋锁,都不允许锁拥有者第二次获得这个锁,如果试图这么做,系统将挂起;
  • 锁的顺序规则 按同样的顺序获得锁; 如果必须获得一个局部锁和一个属于内核更中心位置的锁,则应该首先获取自己的局部锁 ; 如果我们拥有信号量和自旋锁的组合,则必须首先获得信号量;在拥有自旋锁时调用down(可导致休眠)是个严重的错误的。
    : e4 P  S9 ^, y0 E4 ?  L; e
死锁举例

因为自旋锁持有时间非常短,没有直观的现象,下面举一个会导致死锁的实例。

运行条件
  • 虚拟机:vmware

  • OS    :Ubuntu 14

  • 配置  :将虚拟机的处理个数设置为1,否则不会死锁

    ; L: E6 r; p5 P/ _0 k' K9 Z( u* y
微信图片_20200904201955.png 原理

针对单CPU,拥有自旋锁的任务不应该调度会引起休眠的函数,否则会导致死锁。

步骤:
  • 微信图片_20200904202002.png
  • 进程A在open()字符设备后,对应的内核函数会申请自旋锁,此时自旋锁空闲,申请到自旋锁,进程A随即进入执行sleep()函数进入休眠;
  • 在进程A 处于sleep期间,自旋锁一直属于进程A所有;
  • 运行进程B,进程B执行open函数,对应的内核函数也会申请自旋锁,此时自旋锁归进程A所有,所以进程B进入自旋状态;
  • 因为此时抢占已经关闭,系统死锁。
      }1 a9 M  @5 Z9 I4 H

驱动代码如下:

#include <linux/init.h>" g* c: P3 j  N9 F! ~; x! z0 u! G
#include <linux/module.h>
4 H9 F& @$ S3 ]% _#include <linux/kdev_t.h>
1 m# v( N  L7 A. l#include <linux/fs.h>
: h1 R$ L: b; t& g, t#include <linux/cdev.h>
. K. V$ x0 R* U4 R6 S#include <linux/device.h>7 P- D6 |2 D5 ~5 p* h6 g
#include <linux/spinlock.h>
! j% L( B% U" Z; d) U* p7 o$ c+ j
. ]: E/ e. {: F; ~+ i: R: \; ostatic int major = 250;, a% D( w! E2 t% N
static int minor = 0;
/ p8 r2 ?; P$ Jstatic dev_t devno;9 ^. U4 K, p7 |" u; K
static struct cdev cdev;
5 A0 ?  y, P# \1 ystatic struct class *cls;
7 `8 V- L8 x& |! a9 j. {static struct device *test_device;0 G0 W3 j9 w" A3 G6 G7 x7 v
static spinlock_t lock;6 B5 O* }+ L% O4 r2 ^, x3 f6 m
static int flage = 1;+ d9 |+ {+ P, }
+ K; g& j4 s: O& H% v- U: p
#define DEAD 1
) _' y& A5 ]* I9 Tstatic int hello_open (struct inode *inode, struct file *filep)9 f3 T( [5 B- x# D+ b: F" M
{* e" x4 M- E+ M' b6 T
    spin_lock(&lock);
! b- u. ~- U, ~9 l! x! |' R/ l9 ?    if(flage !=1)
7 A: B  d/ W  H' s; l' e5 E    {
8 c7 m/ H* w9 ?         spin_unlock(&lock);
, g6 a& u# [$ j         return -EBUSY;
+ ^. U4 @1 L! ?& W5 m0 Y" a    }' U+ W6 O/ Z( J) U1 @
    flage =0;
; o" F: m. {: K5 P2 s" G( Y3 M3 F4 s2 ~    #if DEAD
  G0 W; H: X& w    #elif
+ O3 F% L; |) M5 ~    spin_unlock(&lock);0 B5 S% F' F6 v# f
    #endif2 c0 f$ ~; S1 {1 W7 a( V% S- h3 t
    return 0;/ D0 K/ x. ~; [9 D3 F; w& g) [* w
}
6 M  p7 z2 [5 ?7 V# e( fstatic int hello_release (struct inode *inode, struct file *filep)  q8 ?1 ]+ @( L2 c* W& B- w6 y
{
" n# b0 ]' _" m8 X/ }    flage = 1;6 V. o; n) R/ f( I
    #if DEAD, Z0 x& o3 ~  F. E+ u* Y
    spin_unlock(&lock);- c( y+ F7 P% o: g9 ?  U2 |, [5 g
    #endif5 ^: p7 O0 ~! h7 F4 O
    return 0;: a5 c; }9 o2 a6 N
}
# c3 u- Y' a  F6 h5 w6 |( |8 vstatic struct file_operations hello_ops =0 e( G* c( e5 {; K
{& B! f) P; ?+ ]. i5 X
    .open = hello_open,
, R% n/ p' H/ d' |) T" A    .release = hello_release,
- E% _" T+ _- g2 J1 [- A, A$ B};, ~. K( T3 ]% E1 H: n& `: Z6 n
static int hello_init(void)3 H% _6 T$ u, H& r/ S8 H
{
: W  w, K5 b8 i8 c* Z  R    int result;
6 J9 \( D1 d( }( M( u6 I7 h    int error;   
- f' m# o8 d8 u2 S    printk("hello_init \n");. }' i- H! _* g, J  f7 G
    result = register_chrdev( major, "hello", &hello_ops);6 r( E& `" s! \6 c: @: f) s
    if(result < 0)
" L( J4 M) d) k  V+ U' Y    {
9 J2 l$ _- r7 k# W% e+ Z* H* t        printk("register_chrdev fail \n");* ~8 D0 J; q1 G8 ]
        return result;) z1 S+ p9 Y' i( [! ^
    }
9 b' W, z; w, G5 B    devno = MKDEV(major,minor);6 C! ?% n3 _* B5 u
    cls = class_create(THIS_MODULE,"helloclass");
! a8 ]  W2 f( E, F8 v) z+ [    if(IS_ERR(cls))
# y) }# T/ [4 h! N    {( _, B' d! v& a
        unregister_chrdev(major,"hello");
" @* a0 n5 T& a6 m" A% k        return result;  w+ T  W! I1 I9 z7 j. h: ^8 l
    }
7 d; z$ B7 ]  d" g# Z# [% w8 f5 m    test_device = device_create(cls,NULL,devno,NULL,"test");2 Q# {' u1 x& u. Q  _( n
    if(IS_ERR(test_device ))
& s9 i6 W7 B' e# D5 q3 Z    {
6 K! D2 O) @2 d/ C0 n5 I( V+ U        class_destroy(cls);
+ N1 v; d" V0 B        unregister_chrdev(major,"hello");
" y# E" @0 Z; d2 r/ k        return result;
1 f/ B% \1 {! {5 o    }
! t' A" i& |1 ^. R! a+ b, e6 F& G    spin_lock_init(&lock);( g% W2 F4 ~& N7 b1 ?- F
    return 0;
" W( K7 R8 ^9 ]. ~( P}
0 ~0 A% ?, |% f9 L8 Ustatic void hello_exit(void)
  J+ C/ s' a: X{
% @; n3 I9 f% @4 Q2 s& ]# Q    printk("hello_exit \n");
1 B7 y4 x$ m  t3 }6 {    device_destroy(cls,devno);   
: T1 z* X- r/ ^5 b7 V. y; G    class_destroy(cls);
4 O5 Q$ y& c7 P1 V- d    unregister_chrdev(major,"hello");
4 ?# t) Z; c; J7 Y1 [+ a) I    return;9 `' O) L) V8 i  Q
}
3 d9 K% P5 w; H; a9 b. j% @2 cmodule_init(hello_init);
5 _) e( ^1 n$ ]. J3 }7 X; i5 imodule_exit(hello_exit);
1 ~$ o( P2 K. _% k* p/ tMODULE_LICENSE("GPL");
8 p/ r1 T5 u0 a+ C. e& x  l* E! j7 z

测试程序如下:

#include <stdio.h>6 v: d$ @3 B, G, B  `; T
#include <sys/types.h>
7 x' `9 Z- H% _4 Y% o% T" N#include <sys/stat.h>3 s% F" }. k; R5 o3 h
#include <fcntl.h>  z4 P- a$ G8 b2 V0 q- L  A7 M! ^* U
main()
" O. s' K7 E* l1 `{, n+ J! \* n' s( \% K3 G4 V1 r
    int fd;3 m2 [  N0 J+ c3 O. ~8 v* C
    fd = open("/dev/test",O_RDWR);/ ~# L8 K% ]* ?: L$ U2 @7 v- [
    if(fd<0)& A' i) F. g. H- ?
    {
, Y( H& Z9 W- U3 @# }4 ]2 @        perror("open fail \n");
, S! j& W$ c. I3 z7 U        return;
) N9 w% y9 b) \, V- b+ `0 C    }
! v: v8 G/ ~' \9 `5 h3 ?    sleep(20);: h: c4 L6 N) z/ Q2 |
    close(fd);   
! {; X6 A1 t( l5 N5 F    printf("open ok \n ");/ Y* i8 G. E0 m0 l$ C
}- q, B- g8 N5 O) ~& e% G

测试步骤:

  • 编译加载内核2 |# n9 f- b# w, y5 d- V
make
: [) m  U" w6 U0 v" d( k. w0 E/ j; cinsmod hello.ko
' p: d$ z( x5 f4 D/ [4 N
  • 运行进程A
      ]5 t6 S" B4 `
gcc test.c -o a
0 c+ N( l) e% B/ t  c. O5 L: j./a
% I( |7 X. H4 G( r: r  u
  • 打开一个新的终端,运行进程B3 i# M( I- Q; M* {( d0 t& v
gcc test.c -o b
# j+ R" a7 d5 @) e./b
6 ^: b( d2 A  A8 F. q) G. v! T

注意,一定要在进程A没有退出的时候运行进程B。

% J) r! m  O6 v# j& T& l% r+ `, g( _
收藏 评论0 发布时间:2020-9-4 20:23

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版