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

Linux 自旋锁spinlock

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

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

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

  • 一个是原地等待
  • 一个是挂起当前进程,调度其他进程执行(睡眠)
    . C! \0 {, \; \* p5 H0 G4 L" x
自旋锁

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

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

自旋锁的优点

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

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

自旋锁的使用

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

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

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

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

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

动态的:

spinlock_t lock;) I1 N/ a" c5 U6 }8 T9 Z* C
spin_lock_init (&lock);9 E7 c, \$ h5 ?  r, U* s

静态的:

DEFINE_SPINLOCK(lock);
4 K2 b, |. P& T6 {; g' e
使用步骤

spinlock的使用很简单:

  • 我们要访问临界资源需要首先申请自旋锁;
  • 获取不到锁就自旋,如果能获得锁就进入临界区;
  • 当自旋锁释放后,自旋在这个锁的任务即可获得锁并进入临界区,退出临界区的任务必须释放自旋锁。. t$ k5 Q6 W$ I8 l7 U2 p+ ?5 M

使用实例

static spinlock_t lock;* |/ `1 N  D9 I' b/ ~
static int flage = 1;
7 D/ w& X4 W( k* D% k8 w/ @8 a0 Q. {7 l" I% B* x" c7 B. M
spin_lock_init(&lock);
/ p6 ]' U' P/ K+ B  h0 X0 R5 g4 \# ]0 |. Q& [5 M- l
static int hello_open (struct inode *inode, struct file *filep)( k2 x/ _' \- k7 k
{
/ b) V- {/ W# \! [0 D$ `  spin_lock(&lock);
. Y  C; u$ \$ V! `+ D0 a- g" E      if(flage !=1)
! ?" J4 W. A' V+ j      {
* l: g8 ~2 z) h! o             spin_unlock(&lock);
: {% Y' n0 I! J7 t             return -EBUSY;
$ j7 E8 l" @4 C/ U      }# d" y4 |; L; j. T/ g1 F; A8 }! Q2 c3 T" \
      flage =0;9 Z  c4 R% d4 z
      spin_unlock(&lock);: G! F  ^/ x/ G+ s8 Z1 y+ L, _

5 x7 G  ~* E- M/ v: F      return 0;. Z/ i) q, L( c( G( z
}4 D* G  O& \  x1 }$ w% U% g) S8 M
static int hello_release (struct inode *inode, struct file *filep)8 K1 F$ b. T2 k  p3 X
{
5 P3 k3 G# V) Y  @  flage = 1;8 P0 u9 U3 `: f- t5 M
  return 0;  ]" c8 Q. h( @" g$ d
}# k1 W" {6 I8 Q7 s# \
补充

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

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

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


    , f2 t$ \' J* F# m1 r# h0 C

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

  • 内核中schedule()函数本身在进来的时候判断是否处于中断上下文:
    . a0 o7 s" |( C$ F. I2 x9 Q5 M5 _7 T
if(unlikely(in_interrupt()))3 [5 B$ R3 a- c6 q- ^$ o1 P4 o
    BUG();
, `2 |  D! [2 a+ H$ Q

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

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

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


    6 ^5 \: j3 H4 m- x/ R3 `


8 {) p' v: `+ x# g2 e& o

自旋锁的死锁

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

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

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

(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中也会访问共享资源 R3 D1 n: }3 W1 d5 e

在这样的场景下,使用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 {: W. l+ s% O1 m# w
   struct raw_spinlock rlock;  
  N; Y1 E2 V$ }( H) @} spinlock_t;+ u$ `, i- }# K; ~/ V5 B- C1 B* E
typedef struct raw_spinlock {# {+ h. l) w4 ~( k; l
   arch_spinlock_t raw_lock;
9 n0 M* V: d* Q9 u} raw_spinlock_t;
2 \1 J7 o" J; l+ S. {* c! J+ Z/ t

通用(适用于各种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相关的,
    8 l) H+ U" F) q  Z
ARM 结构体系 arch_spin_lock 接口实现加锁

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

static inline void arch_spin_lock(arch_spinlock_t *lock)& |' e+ ~+ M+ a- l9 t5 P
{4 N( U9 Y  }$ G) ?1 e6 p
   unsigned long tmp;. e4 I* m0 Y% {. w$ |
   u32 newval;0 q6 B/ u( [$ M3 y" J( W6 {
* p/ s, q7 a: ?) V; n. j
   arch_spinlock_t lockval;
7 @, Y2 j  H; [5 A% q( @   prefetchw(&lock->slock);---------(0)5 _- v/ ^6 f" i) X
   __asm__ __volatile__(
0 f4 J& C1 i0 u7 @; A"1:    ldrex    %0, [%3]\n"---------(1)
  m+ Z$ ~: L9 R, @( T"    add    %1, %0, %4\n" ----------(2)
4 R' A* i/ X; |/ h9 Y: `"    strex    %2, %1,[%3]\n"---------(3)
& f9 a: G! k6 U8 i# S/ U"    teq    %2, #0\n"-------------(4)4 ~" ?7 i* N4 r
"    bne    1b"
# K2 m8 d3 E$ B   : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)0 G. y+ c$ |% N( F
   : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)' n7 u- g. [4 _0 T4 ?9 f
   : "cc");
7 U+ G. [3 j' B9 M+ d  A; A   while (lockval.tickets.next != lockval.tickets.owner) {----(5)' s) }1 ~- R; @- \4 R( ?
       wfe();------------(6)
+ m& q* R  v* j* Y3 ~8 P0 N lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);----(7). _( ^1 m7 ~8 J2 L2 U3 C0 t/ Y+ e
   }   ' ~& G9 Q$ X! h$ x8 G
    smp_mb();-----------(8)5 O3 Q* ?, y$ x
}
- A4 C! T1 B( Z1 T
(0)和preloading cache相关的操作,主要是为了性能考虑6 _% M: c( ]( o0 r3 L
(1)lockval = lock->slock (如果lock->slock没有被其他处理器独占,则标记当前执行处理器对lock->slock地址的独占访问;否则不影响)
3 C+ K0 S7 q* z" z7 L0 q) R(2)newval = lockval + (1 << TICKET_SHIFT)- L9 M2 K$ A4 ^: I- f0 i, H& c
(3)strex tmp, newval, [&lock->slock] (如果当前执行处理器没有独占lock->slock地址的访问,不进行存储,返回1给temp;如果当前处理器已经独占lock->slock内存访问,则对内存进行写,返回0给temp,清除独占标记) lock->tickets.next = lock->tickets.next + 1
6 C- D& G& w; h& d8 y( [1 I(4)检查是否写入成功 lockval.tickets.next1 _. G- q" Q; r) L, n& o: N
(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重新赋值
: s4 u2 V/ y/ r  p(6)暂时中断挂起执行。如果当前spin lock的状态是locked,那么调用wfe进入等待状态。更具体的细节请参考ARM WFI和WFE指令中的描述。3 G+ E( Z) _: I! k) d
(7)其他的CPU唤醒了本cpu的执行,说明owner发生了变化,该新的own赋给lockval,然后继续判断spin lock的状态,也就是回到step 5。5 {- u4 C% x! T9 c7 f+ I, _; d
(8)memory barrier的操作,具体可以参考memory barrier中的描述。
' O/ b; Q6 `0 U. {
释放锁static inline void arch_spin_unlock(arch_spinlock_t *lock)
5 D8 Q5 R( A: a# v{
8 R. c3 z) `# v smp_mb();  % I) J8 N$ z+ D7 r+ N' {- A
lock->tickets.owner++; ---------------------- (0)+ N' f6 r7 u7 l
dsb_sev(); ---------------------------------- (1)& g3 J% J5 f/ c% U: I
}; G3 J$ F. \( I, W& L- P7 _, S0 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等待的处理器
    / x/ Y, l9 N  s
自旋锁导致死锁实例死锁的2种情况

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

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

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

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

运行条件
  • 虚拟机:vmware

  • OS    :Ubuntu 14

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

    - e, O! {: p- l5 v! V2 u* }
微信图片_20200904201955.png 原理

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

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

驱动代码如下:

#include <linux/init.h>
3 V9 g( L7 z2 l! @/ A# j* f#include <linux/module.h>
5 g. J1 |: s1 `7 r3 P9 D' Y#include <linux/kdev_t.h>8 M/ v9 M1 K8 R- e& ?
#include <linux/fs.h>; D( i8 v+ W$ S* c0 q( o, A
#include <linux/cdev.h>
8 p3 E2 [7 D" H7 X3 \#include <linux/device.h>
7 w; l% K. I( w#include <linux/spinlock.h>6 S9 j2 j# d/ g9 R5 N7 p# {- ~* V8 Q

% S) S$ o2 b+ u: a0 q" _static int major = 250;
( ?- o/ q9 M1 a4 o( Mstatic int minor = 0;4 G& u4 m: H, ?5 W
static dev_t devno;
/ Z1 U! p9 n5 X/ E+ Sstatic struct cdev cdev;
7 z$ ~4 k! O$ C3 L% \static struct class *cls;2 j& S1 f0 y/ z3 o( K
static struct device *test_device;4 ]# k; X9 H' N- F1 K2 K5 ]' p1 E) }6 A
static spinlock_t lock;7 i8 Z/ o' o% W
static int flage = 1;
8 a3 |" Y9 y0 q4 j) k' B# ]( e, H" R& P! s9 s* d
#define DEAD 1
2 _' E/ d! p" e1 h: J4 p. ^% tstatic int hello_open (struct inode *inode, struct file *filep)8 v* ?7 Z9 @5 p- ^. S
{' |6 l5 o% K- d) v2 h- \) S( ~
    spin_lock(&lock);: w. D. j9 h; A" y" g& o! Q
    if(flage !=1)+ Z( M% `, s# m) [$ i0 s1 C" P
    {
- @, I% P& F! {! [4 T/ F! U5 Y# g         spin_unlock(&lock);: S6 t! {. o8 Z; t- n& g
         return -EBUSY;
/ Y6 X+ R$ V) ?: ?& a    }' ~" d3 R1 A( W- B( ~. t
    flage =0;: K1 e% q! y) v
    #if DEAD
2 v( ^, u4 U2 w( O& W8 A4 r* F( n    #elif
3 y; Q) s* x$ L& `* X    spin_unlock(&lock);
* ?8 r! j8 V' u& D8 C) H+ D    #endif" `, s7 ^+ r- n- ?& [
    return 0;. `8 Z7 O4 ~% r  {9 o
}  N( u! K$ x# f; m: I$ y
static int hello_release (struct inode *inode, struct file *filep)
) i- i! y8 w0 Y  _4 L/ z/ G( s4 z2 V{2 G6 |$ z' C; u! V
    flage = 1;
# k' q% E3 H0 z    #if DEAD
- Q  p1 y$ B1 q* U" J    spin_unlock(&lock);
) A% N0 O) m  b# ~5 u# ]    #endif7 _2 ?0 ^5 Z7 U6 M9 X; [
    return 0;# b" G! e. L" N6 Z; F% m+ q$ [
}
- e  @$ H' u- `9 Ystatic struct file_operations hello_ops =& d' j; ]0 n/ G* H
{3 {9 I$ w* |+ u, X! M& _+ [
    .open = hello_open,* r/ f3 y7 \+ l2 Z" \/ \8 B
    .release = hello_release,
6 z7 x8 P1 [& L. J+ d};
& d9 B7 C2 a) F$ z# a* r2 ]static int hello_init(void)2 E: r; d7 c. W! c) y
{
" c1 R/ S9 X; K: W) r. [' Y    int result;
0 x+ c: z$ ^$ s0 W  r    int error;    3 q( C' X- W& T/ j
    printk("hello_init \n");# z$ a5 b8 x" t, C" P6 P3 I% [, }3 D0 G
    result = register_chrdev( major, "hello", &hello_ops);+ W) R( a' Z1 _8 ^( i; z
    if(result < 0)
! a$ x3 v( d4 L    {" Z9 ]5 O  x4 Y) U9 J2 J- C
        printk("register_chrdev fail \n");
, P/ @# b$ |% G- M        return result;
  Q+ h7 w: ^- V! m- b' p6 G4 P    }/ w4 y# f/ L+ `3 K3 K. W
    devno = MKDEV(major,minor);
+ y  ~# N/ ]' F8 t    cls = class_create(THIS_MODULE,"helloclass");0 n  v' V; n' a' x; ~" T% x5 b7 J+ l
    if(IS_ERR(cls))
) R1 g4 P  o3 z3 q$ Q: c8 s  a7 T    {$ e( Z2 x  Y  L  F& V1 v3 ~6 n2 J
        unregister_chrdev(major,"hello");8 Z) V! L% S4 F  e( F
        return result;
* _) Y' D5 F# V    }8 g, |7 e* F% B; R  F  |; H" [
    test_device = device_create(cls,NULL,devno,NULL,"test");* Z) J" v8 z1 }2 s$ \, s
    if(IS_ERR(test_device ))
& r5 V, X* P; O0 k9 x! \( _5 T+ D$ k    {: F8 p) Z3 M: e3 p" l$ c/ S' `
        class_destroy(cls);
" P( n% B7 R" C        unregister_chrdev(major,"hello");% f  i# C4 W* N% o  b
        return result;( V  i4 y, l3 J% Z9 h5 U
    }/ \  I2 d' q- g; a0 d& U* ~
    spin_lock_init(&lock);) B- m4 O7 e3 H: n0 I2 V5 B
    return 0;+ v/ W( t. s! g& d; F+ |8 G* G
}# Q! g) B* u" L4 r. h  o
static void hello_exit(void)  _3 Q1 U: ?/ ]( \/ M* ]
{5 j0 P2 {# Z; P
    printk("hello_exit \n");
! X  o2 |5 V) ?! M0 y: K    device_destroy(cls,devno);   
3 H: x& N$ V0 l8 a3 g- u% c! j    class_destroy(cls);, V- n# p; }( X$ y! G
    unregister_chrdev(major,"hello");- r5 l; H- q, i* b) \- y
    return;5 ^. V( W! G; |$ ?+ E6 d$ X
}
2 Z* a( r" g" mmodule_init(hello_init);6 l7 G$ K) G0 F! ?
module_exit(hello_exit);
' |' W$ ]) R% M6 |/ gMODULE_LICENSE("GPL");
( D4 ]: h) L) W5 {0 |7 V# s! q

测试程序如下:

#include <stdio.h>
0 p1 p# i" e" J5 M3 x. t) D#include <sys/types.h>( _3 g* R; W6 s; K
#include <sys/stat.h>: z0 F4 @+ r3 v! @, y/ ]
#include <fcntl.h>- e1 D3 m( B+ W! d5 K/ N
main()2 W/ M" U/ B* X4 Z8 G% E
{5 ]: ~3 ?. J4 A. }. O
    int fd;
  V  W" N0 k& ^3 c( V6 Z; Y    fd = open("/dev/test",O_RDWR);
( a+ ^5 Y) l# `+ O# s: j) L8 ^8 a    if(fd<0)
: Q% t$ n3 j" l" ^    {
6 J/ z: f+ r( o& C1 m$ Z        perror("open fail \n");
9 L4 C3 t  o9 i. Q; P        return;+ V* V. F+ A2 t' w4 U2 m$ D: g
    }7 n; C' z% I" E) H, [/ I
    sleep(20);0 D( P* N1 q( R4 E' R; @# n
    close(fd);   
& o: c3 }0 @) ]) g( _) v    printf("open ok \n ");. d4 [# [# M7 m0 T/ a  p
}
- [. w6 c! c$ t6 H: `

测试步骤:

  • 编译加载内核
    2 G- y% `4 H+ r7 p
make
+ J2 h0 U& M7 K; O; K$ qinsmod hello.ko) ^  ~/ ~4 x* q: q
  • 运行进程A
    / u5 b6 c, x) o; @; X& W
gcc test.c -o a
! ~, x$ f" H4 _- ?, B' I6 U8 q./a , R3 x8 g+ F9 m1 l! x  D
  • 打开一个新的终端,运行进程B; ~# d3 R6 Z# n. W( s4 w! h7 A
gcc test.c -o b( [  Z& Z# G# L; X3 P$ J' E, P
./b, o, `. `' d+ U2 V7 J

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


: ]$ |- l% c3 u0 J: G
收藏 评论0 发布时间:2020-9-4 20:23

举报

0个回答

所属标签

相似分享

关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版