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

Linux 自旋锁spinlock

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

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

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

  • 一个是原地等待
  • 一个是挂起当前进程,调度其他进程执行(睡眠)( v2 S+ h6 L2 I
自旋锁

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

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

自旋锁的优点

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

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

自旋锁的使用

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

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

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

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

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

动态的:

spinlock_t lock;
0 Z9 g) q" N- h- Sspin_lock_init (&lock);( ?) r! r. n4 U9 [* I

静态的:

DEFINE_SPINLOCK(lock);5 C* L4 T/ ^, n2 C& s# I4 v
使用步骤

spinlock的使用很简单:

  • 我们要访问临界资源需要首先申请自旋锁;
  • 获取不到锁就自旋,如果能获得锁就进入临界区;
  • 当自旋锁释放后,自旋在这个锁的任务即可获得锁并进入临界区,退出临界区的任务必须释放自旋锁。7 l2 K# |; P1 e6 {" [, m* x

使用实例

static spinlock_t lock;
& _0 x& F0 B) B* @: S! E3 astatic int flage = 1;) |$ v( _1 i3 C

7 C- o* M( }+ s* t, Fspin_lock_init(&lock);
- i: F% P+ V% x  _2 A/ w* u9 j, P, V) B. E9 T( S5 `
static int hello_open (struct inode *inode, struct file *filep)
8 P6 G& o4 n# J; v0 g{
2 `$ m% D* k+ H- s  spin_lock(&lock);: V. {3 v8 [0 F3 W: S9 k, E
      if(flage !=1)9 j$ m7 ]$ N1 v  f' }
      {3 a% F, T. K; G4 R8 H
             spin_unlock(&lock);! P, K( z, n; ~" z2 N) U
             return -EBUSY;, V8 \$ P& I2 b5 @" o" T
      }# l7 q  C  B2 Y; ^6 ^
      flage =0;
% U) F* H/ q0 Y      spin_unlock(&lock);
; S8 q% [" s( a" |* w; T
3 R2 V" k  [+ ?6 U! C  X+ I      return 0;4 z$ }: h1 K2 e& t5 G
}9 O- ?$ n5 u! x5 K
static int hello_release (struct inode *inode, struct file *filep)
0 e2 z! g/ x# h' p! c{
3 w% E( K+ I* I( |0 S  flage = 1;( n* p: _# d/ C" n3 J4 O$ C3 ]1 T
  return 0;
  t$ z) s0 {2 `}
% f- ?8 h2 S0 L' I$ L
补充

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

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

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

    ( m( t( a! l7 l. T

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

  • 内核中schedule()函数本身在进来的时候判断是否处于中断上下文:
    ' c' _) e) r. d
if(unlikely(in_interrupt()))
3 v; ?$ R& A2 C2 o1 a    BUG();, _6 U4 y, w( W  G3 b$ p1 l

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

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

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


    - w3 L% f0 w* ?/ S! S1 s% n1 V


" w3 k6 G, U" D4 q! }

自旋锁的死锁

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

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

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

(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中也会访问共享资源 R4 F. Z; x, m- r, |7 d, A

在这样的场景下,使用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 {
$ Q; u6 P/ E: d& |- Z  K   struct raw_spinlock rlock;  
- \9 I+ t1 t; ?} spinlock_t;
8 V+ S  r8 k6 jtypedef struct raw_spinlock {8 U7 Z8 _& t* n& t
   arch_spinlock_t raw_lock;' c( [! h6 o1 ~4 B, B" v
} raw_spinlock_t;0 f4 y3 t( X- \

通用(适用于各种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相关的,
      ?) x& y: H5 A" E/ B$ f; Z2 e
ARM 结构体系 arch_spin_lock 接口实现加锁

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

static inline void arch_spin_lock(arch_spinlock_t *lock)
& {1 K( y  P5 z. S3 |6 V1 V' g- N& v{
7 Z# u& M" L8 j" c4 q   unsigned long tmp;
' [) B# v7 n; e# d   u32 newval;% Y8 S8 _9 P  P8 D. A

6 A0 F1 a# i/ {4 R6 O. O. W   arch_spinlock_t lockval;
' l+ x5 L9 s7 u+ c. Q" ?   prefetchw(&lock->slock);---------(0)4 ~# D6 r5 {% S) x
   __asm__ __volatile__(
0 Q  N! N- ^8 M5 y% S"1:    ldrex    %0, [%3]\n"---------(1)& L7 P2 E* Y, ^2 ]2 L4 i
"    add    %1, %0, %4\n" ----------(2)
9 ~: O/ P4 N- i9 M5 d4 g"    strex    %2, %1,[%3]\n"---------(3)
1 S: |( w- r1 G, q' v7 X"    teq    %2, #0\n"-------------(4)
5 l  A$ J( d% q( [6 v- X"    bne    1b"( [3 J! I, U: J) ?
   : "=&r" (lockval), "=&r" (newval), "=&r" (tmp)# {# o# m+ M: X+ ]
   : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)$ [% Y; H( l) b
   : "cc");8 U1 k8 d# r! E6 x$ Y: z
   while (lockval.tickets.next != lockval.tickets.owner) {----(5)+ b5 `! Z4 s- q2 r5 r; Z
       wfe();------------(6)
8 m1 A% A' v9 c+ {0 A3 \ lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);----(7)
! z& j! _& C8 U" a   }   ) `' K. r. B9 q( t0 A% r3 F) A* x1 c
    smp_mb();-----------(8)  p5 p. {8 k4 X$ x: h
}
; Q4 S, _- P5 P4 X4 e$ \$ g
(0)和preloading cache相关的操作,主要是为了性能考虑% d+ B: i- q- \) _$ S
(1)lockval = lock->slock (如果lock->slock没有被其他处理器独占,则标记当前执行处理器对lock->slock地址的独占访问;否则不影响)4 L( Q" D5 c, e9 j3 }. j
(2)newval = lockval + (1 << TICKET_SHIFT)
; s  B$ A. J$ l' o, {(3)strex tmp, newval, [&lock->slock] (如果当前执行处理器没有独占lock->slock地址的访问,不进行存储,返回1给temp;如果当前处理器已经独占lock->slock内存访问,则对内存进行写,返回0给temp,清除独占标记) lock->tickets.next = lock->tickets.next + 1
% S; N5 m) r  Y6 B  A(4)检查是否写入成功 lockval.tickets.next
$ |2 j9 z% g+ u" m" M(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重新赋值
; d* F% N" K# B$ e0 e5 T) |(6)暂时中断挂起执行。如果当前spin lock的状态是locked,那么调用wfe进入等待状态。更具体的细节请参考ARM WFI和WFE指令中的描述。
7 w+ `) Z- J5 L, |+ k4 U, V(7)其他的CPU唤醒了本cpu的执行,说明owner发生了变化,该新的own赋给lockval,然后继续判断spin lock的状态,也就是回到step 5。
$ q* i( s# o& m4 h& d(8)memory barrier的操作,具体可以参考memory barrier中的描述。
& `- j; l* M7 C9 y
释放锁static inline void arch_spin_unlock(arch_spinlock_t *lock)
/ e/ D% R+ G0 T{# J6 ^2 U8 @- u8 ~
smp_mb();  - K& B! ]- r/ c: h
lock->tickets.owner++; ---------------------- (0)
! M& a; n; R; ]7 W1 q/ c dsb_sev(); ---------------------------------- (1); P8 b* p& ?6 O. a/ `
}5 S9 `. ^7 p8 N5 \5 e# o. ^
  • (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等待的处理器
    $ C& K+ b; d+ i( G
自旋锁导致死锁实例死锁的2种情况

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

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

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

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

运行条件
  • 虚拟机:vmware

  • OS    :Ubuntu 14

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


    $ B% R/ b9 ?1 t2 p& E
微信图片_20200904201955.png 原理

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

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

驱动代码如下:

#include <linux/init.h>. I& x  t/ g* X3 E1 t$ I
#include <linux/module.h>7 _1 C: M; V; H
#include <linux/kdev_t.h>
# j( w6 i( V2 L! m#include <linux/fs.h>
5 k' e% W, e- t7 t# k+ G9 F; I#include <linux/cdev.h>( w& W$ p) t- W1 C3 R* u& k
#include <linux/device.h>3 P7 ~/ ~7 ]( v( s
#include <linux/spinlock.h>
- K' {" j* u; b  L. G" u+ l" \* D, t5 \( O3 {7 s3 k. D! m- x
static int major = 250;
: s: M4 L( p0 _8 jstatic int minor = 0;2 D4 A) W# Z6 R0 e: b+ o/ F5 l5 W
static dev_t devno;, W+ e; Z; X7 E5 b
static struct cdev cdev;: Z! g2 g6 h8 z4 y% F, ?( t6 i
static struct class *cls;+ F: Y6 h6 N# q
static struct device *test_device;
) d* s- f6 z  @  f$ @static spinlock_t lock;
/ f4 O& Z# Z. I0 B3 l( A+ ]% Gstatic int flage = 1;
7 d% X! \, t( l* u7 s: u1 v) [: r- _1 j1 W
#define DEAD 15 G8 t0 {0 J+ R1 @0 G- ~! Q
static int hello_open (struct inode *inode, struct file *filep): F* X% S5 Z+ l( E8 P7 B( t# `
{
2 G7 [0 l% t; N& w% Y2 H/ J' U" W7 G0 N    spin_lock(&lock);
/ n" T- g4 `  x9 }$ N    if(flage !=1): L& P$ u7 Q" g0 m6 p) G# a" M1 p
    {
4 ]# z+ [: R3 Y; ^$ r$ q         spin_unlock(&lock);: h4 J* |5 J6 ]! A
         return -EBUSY;, P/ G5 \5 \- O3 [8 _; D
    }6 c- j& T. l" M  v$ d* Y  ^: ?$ `! s
    flage =0;
- u; h5 j' u) T& s    #if DEAD5 y+ k4 Q" B. o7 y
    #elif
) _* O, o* Q) R3 I0 H' b    spin_unlock(&lock);
0 T5 B$ s5 E) C6 d    #endif
( h6 S: t. e' v& D    return 0;2 N0 [$ I# [' x) w
}
0 [0 f& Y: r: m' n9 nstatic int hello_release (struct inode *inode, struct file *filep)! ]- K/ n" }! P) U
{
8 B8 m6 U8 H' u1 b: u    flage = 1;: C# r. V8 T* f0 c
    #if DEAD
' b, [2 V/ G7 }3 |    spin_unlock(&lock);# n8 O/ m% q! y3 @; ?% `
    #endif
$ v4 L% B2 X9 {' k' `    return 0;
  e  m8 A  R8 N7 o5 p}( D3 @1 _* F. ^( `7 @% ~; u
static struct file_operations hello_ops =
4 U" C) P  K% f& {{# l" a" I) B' H  |: k1 S
    .open = hello_open,
* u3 H. B8 P' w- E+ U' ^, Q    .release = hello_release,
. l( E- V8 i* p5 T, x/ T0 e$ r};
, }8 u& z9 x3 Y1 G6 y  u& Hstatic int hello_init(void). n8 I( p+ ^4 X: @! t3 ?
{
" r1 G( R! W+ f: r; \  n% x    int result;' f9 ?- m) X, f3 ?% O/ O
    int error;   
3 B- E: D- \* S7 }% ~0 V1 J    printk("hello_init \n");) y4 X4 ^' N( P  L. p- h
    result = register_chrdev( major, "hello", &hello_ops);5 {- @4 q7 ~9 ~* W7 G, i7 p- n
    if(result < 0)
$ ]& P7 C2 G- d; ]5 u" e; j  i; b    {) n) g& _# \+ M% x! x) `8 O, I
        printk("register_chrdev fail \n");
( I/ l3 L/ r- k" v8 h        return result;
  d) P# z9 `% v4 w6 s- H4 Q    }
; h* V" \1 ^  v6 a2 D    devno = MKDEV(major,minor);
% ^3 _! b7 d3 P: v" ^    cls = class_create(THIS_MODULE,"helloclass");
5 w( f/ s6 h7 m  j7 d    if(IS_ERR(cls))
" a% i5 w9 M% V% C0 X    {
6 q7 ^6 j8 ?6 i, s        unregister_chrdev(major,"hello");$ U  j) l3 F7 x6 N' Q" D
        return result;6 ?- z7 ~/ i' u! i( Y
    }, m+ u% y" c. b6 o* y
    test_device = device_create(cls,NULL,devno,NULL,"test");
3 e: j' p% D$ M. y( J6 t, }0 f    if(IS_ERR(test_device ))1 a7 c- ^; q1 P7 Y+ d6 A' F( x
    {  w5 W6 P! ?: S. A
        class_destroy(cls);
1 p8 D9 ?- O* j( ^  T4 D( e        unregister_chrdev(major,"hello");
- d1 }* `& w+ A4 K  Q8 _2 ^% k% g        return result;
( }5 g4 z  T; j& X* P    }9 Q- _# A0 {) q2 P) ~( @* m2 p
    spin_lock_init(&lock);3 o- `. O0 e  n/ U% g( e# d. q
    return 0;6 G; d  a# ~) U& U' t& l9 N
}
6 W& O; {8 d) Z9 Kstatic void hello_exit(void)
) ^; z9 g1 l9 h5 F$ P( {7 M{& m0 J% V& Q9 c9 u: ^, O7 j/ |) {
    printk("hello_exit \n");/ I8 Z$ V$ }8 s( @6 ?2 z5 F* B
    device_destroy(cls,devno);   
, L& F3 Q, I; N3 o) b) a    class_destroy(cls);
4 l+ V' ^* R  R    unregister_chrdev(major,"hello");% N+ m" S5 j6 }! |+ [8 A" B
    return;
6 N. c9 c+ b  i: ^# c. N. |}+ y9 {3 s- ~1 F; @. Q2 R
module_init(hello_init);  i* h7 f/ ]2 K4 X+ _- s
module_exit(hello_exit);5 M6 R3 C  R) E9 P( t3 O
MODULE_LICENSE("GPL");2 C/ n7 e% [, ^# ~7 ~/ b0 t; |- Z' M

测试程序如下:

#include <stdio.h>
! d; T( W% Q4 x* ]#include <sys/types.h>* V! C5 J  Z- Z
#include <sys/stat.h>) W$ Q% A: Y* A+ ^( k/ w* ]# I
#include <fcntl.h>
4 l1 k9 d1 @5 i) X! I5 L3 Dmain()8 ^3 L7 P+ {7 b
{7 ]& t5 x  X5 a; I/ C# o' D  _
    int fd;
# s. ]" n; U- p! g    fd = open("/dev/test",O_RDWR);
0 W) D( M& x/ ~2 s2 i& ?    if(fd<0)' c) ?9 G  @  S
    {
5 l# L1 }& [9 @4 p2 F. n. B        perror("open fail \n");
3 P4 X# e9 B+ s9 ?0 q& M        return;
! R+ V4 i# P" ]: ?, A. Z( o1 ?) X7 o    }  Q" Z% o( b/ X7 @5 K
    sleep(20);
/ [8 v- |, Z- b1 O6 x    close(fd);   3 y9 `. H( e& b& [$ i
    printf("open ok \n ");1 P( n- q3 P  p: C; l/ v
}! Q/ W. }9 ]. S9 @& M

测试步骤:

  • 编译加载内核+ Y6 E$ w% ]  T9 W
make* g3 M0 i3 }% B+ s5 j& x
insmod hello.ko
$ q4 P) a6 h; f+ G* [
  • 运行进程A; @+ ?; b9 U* k- y
gcc test.c -o a# `6 c0 X+ f7 U# h6 A
./a + h( S- `' Q! J$ q( w3 S
  • 打开一个新的终端,运行进程B
    & n+ u1 `! `- S4 v3 ^* p9 s6 S
gcc test.c -o b
, a5 \" Y' |: ~$ Z./b
$ U9 N# ~8 a, j: Q2 [5 q0 \) W

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


6 w/ R  h3 Y6 B8 x$ l, @
赞 收藏 评论0 发布时间:2020-9-4 20:23

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版