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

Linux 自旋锁spinlock

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

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

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

  • 一个是原地等待
  • 一个是挂起当前进程,调度其他进程执行(睡眠)5 h$ |! f- w! ]: ^
自旋锁

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

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

自旋锁的优点

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

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

自旋锁的使用

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

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

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

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

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

动态的:

spinlock_t lock;" h3 I) b" ?7 C8 |; {0 T! v
spin_lock_init (&lock);) @6 X( U' ~% V- Z* e

静态的:

DEFINE_SPINLOCK(lock);" _, P9 B7 ?/ k
使用步骤

spinlock的使用很简单:

  • 我们要访问临界资源需要首先申请自旋锁;
  • 获取不到锁就自旋,如果能获得锁就进入临界区;
  • 当自旋锁释放后,自旋在这个锁的任务即可获得锁并进入临界区,退出临界区的任务必须释放自旋锁。" q/ g3 o9 }& }  Q1 D

使用实例

static spinlock_t lock;; c. l2 l! M" k- x( O! K
static int flage = 1;* p1 ~% g) L# p! y6 y2 w; b4 ^

% @: F6 r5 R; V$ Bspin_lock_init(&lock);
# ]7 h/ W$ ^5 R8 h( d# L0 x  {  h( w
static int hello_open (struct inode *inode, struct file *filep)7 W$ x+ m; ^# n* U$ T2 N# ?, G. y
{" g: z. Q9 d4 d  Y: B0 {
  spin_lock(&lock);3 D) v' K+ w' |& s" i, a
      if(flage !=1)4 |, A* B  t( t
      {
6 P4 A* E3 D6 X" }! W: r, Y             spin_unlock(&lock);) e. C4 T, X9 c1 b; c& l
             return -EBUSY;
  N7 S* {3 U* V' ]. A4 q      }
/ R% Y4 O; h' F      flage =0;
" R( Q6 Z( v7 ~9 u: N      spin_unlock(&lock);
% y1 }5 z" ]4 J2 _
: j+ Q- f3 g, \; b      return 0;
5 k8 C# G/ ^  @" T3 [; Z& r}
% r, }. y  V' T: U, N9 T) j& bstatic int hello_release (struct inode *inode, struct file *filep)! U) J2 T- }  N$ ~/ g( I$ G1 g
{6 m' R2 F' ~# x/ O6 E
  flage = 1;
" F4 T) L5 ]$ e0 _  return 0;
* o8 J  ?& P; Y) w" `3 v  X" ]: u* p}
5 ^- w4 Q1 `8 H
补充

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

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

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


    8 m* ^7 l6 R. v' L- P( D$ j

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

  • 内核中schedule()函数本身在进来的时候判断是否处于中断上下文:0 B5 l/ |7 |3 ]5 E0 G5 b
if(unlikely(in_interrupt()))- m& v; v/ U) H1 P: e
    BUG();
/ D- l$ N1 V" Z/ h: n1 L' ~

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

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

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


    ( ~+ T$ T. D+ `) v+ [  Z

% i, c2 {9 a8 R  V

自旋锁的死锁

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

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

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

(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; f: |7 }+ `5 F5 x

在这样的场景下,使用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 {
! l* u6 W1 @7 ?& [6 s2 _   struct raw_spinlock rlock;  ; U. o' _4 C( [/ M3 z
} spinlock_t;
$ O3 K) N$ y. B9 M, ?: X5 z; Htypedef struct raw_spinlock {2 [7 E' [! u1 O
   arch_spinlock_t raw_lock;
& F1 R! K% P( c% n4 S3 v$ c5 }! G} raw_spinlock_t;
, |9 j3 n8 M! o

通用(适用于各种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相关的,
    * f5 l( Q0 S, W+ @1 ]; _
ARM 结构体系 arch_spin_lock 接口实现加锁

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

static inline void arch_spin_lock(arch_spinlock_t *lock)
+ b5 `2 [; Q3 T- Y+ g2 ^{& h' a9 t' m) o/ W: V6 z( L% Y
   unsigned long tmp;+ y" M2 U# T5 P0 V. r# E
   u32 newval;
, H3 x( u: P* ^- i7 r9 I- r( t1 j+ Z  A# b2 m, _
   arch_spinlock_t lockval;+ R2 a% h$ C4 t+ {
   prefetchw(&lock->slock);---------(0)& V' s9 `( z) x& E2 r7 `( R
   __asm__ __volatile__(
: C  q- y9 R8 b* D* {"1:    ldrex    %0, [%3]\n"---------(1)
8 L$ E  ~8 t  a5 A2 X) d"    add    %1, %0, %4\n" ----------(2)
3 L; t) ^6 J( k, g: l& ^"    strex    %2, %1,[%3]\n"---------(3)
3 _3 r/ z" w& Z! A"    teq    %2, #0\n"-------------(4)
7 \  k  e6 w, s3 I3 }"    bne    1b"
! M4 F) H" x# b8 }; M2 j   : "=&r" (lockval), "=&r" (newval), "=&r" (tmp): i# D' p* |) j- v
   : "r" (&lock->slock), "I" (1 << TICKET_SHIFT)  K& N( u& G3 H6 d$ O2 j
   : "cc");& W% V9 M+ w+ s+ v
   while (lockval.tickets.next != lockval.tickets.owner) {----(5)& W/ F4 S8 U4 G3 W, \
       wfe();------------(6)
* w6 r7 U+ C! X0 W( r8 u5 `) c lockval.tickets.owner = ACCESS_ONCE(lock->tickets.owner);----(7)
! x6 ^( C4 ~/ L" P( t   }   3 J2 Y  j9 l3 C& W4 V
    smp_mb();-----------(8)
0 P1 J2 G9 a& h6 U+ Z  v0 d}
  S4 `" ~' v# L; z
(0)和preloading cache相关的操作,主要是为了性能考虑/ j) n7 A& x& s1 |
(1)lockval = lock->slock (如果lock->slock没有被其他处理器独占,则标记当前执行处理器对lock->slock地址的独占访问;否则不影响)
) ^+ ]) H0 ]' _! A3 f# f; N% P" [(2)newval = lockval + (1 << TICKET_SHIFT)
2 v- M' _, S; t- r% }(3)strex tmp, newval, [&lock->slock] (如果当前执行处理器没有独占lock->slock地址的访问,不进行存储,返回1给temp;如果当前处理器已经独占lock->slock内存访问,则对内存进行写,返回0给temp,清除独占标记) lock->tickets.next = lock->tickets.next + 1. B! l' E4 b2 q7 {: r
(4)检查是否写入成功 lockval.tickets.next4 U9 G9 I! J8 y( S
(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重新赋值
! C  k+ Z8 x3 ~* R; H/ E; a, @(6)暂时中断挂起执行。如果当前spin lock的状态是locked,那么调用wfe进入等待状态。更具体的细节请参考ARM WFI和WFE指令中的描述。
4 G2 l+ d: y3 X2 G6 F(7)其他的CPU唤醒了本cpu的执行,说明owner发生了变化,该新的own赋给lockval,然后继续判断spin lock的状态,也就是回到step 5。0 |% t$ e  W1 D1 I  X+ E
(8)memory barrier的操作,具体可以参考memory barrier中的描述。3 c9 C9 i; U; R5 X* b3 v+ l3 d
释放锁static inline void arch_spin_unlock(arch_spinlock_t *lock). U' U1 u' ]& A  f1 X
{
/ m* r: E- b* W2 t6 k# u! } smp_mb();  " D9 \0 O; |# q5 T. d
lock->tickets.owner++; ---------------------- (0)  {( A0 z. |6 z$ T( c+ e2 q
dsb_sev(); ---------------------------------- (1)& \8 X( [2 H% \3 D( I8 G
}
: p2 s) p+ j3 I' ~% e- Z8 K% Z# m3 X
  • (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等待的处理器  N/ S1 [( a" q! u
自旋锁导致死锁实例死锁的2种情况

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

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

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

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

运行条件
  • 虚拟机:vmware

  • OS    :Ubuntu 14

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


    * U" l: R* ?/ O% W
微信图片_20200904201955.png 原理

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

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

驱动代码如下:

#include <linux/init.h>
/ q( o" a& C0 k#include <linux/module.h>
, B6 {- Y5 E' p#include <linux/kdev_t.h>
5 t7 C! R9 o4 j; K+ l#include <linux/fs.h>$ \% X' Y' Z3 [
#include <linux/cdev.h>
& X7 @, R7 K0 ?#include <linux/device.h>
3 P0 h5 b8 ]5 Z0 v+ B, u9 G#include <linux/spinlock.h>
3 z$ i2 `$ V$ M( e
# o0 ]) @; W; z( b- Estatic int major = 250;# ?! L# M& O% b3 k3 F% F' L2 a
static int minor = 0;6 j1 c$ q% y/ @* i
static dev_t devno;4 j6 m" ?& t+ B& L$ ]# W
static struct cdev cdev;2 p  W! v& c) }
static struct class *cls;
  p6 s- g5 d" A# v7 s& |# Xstatic struct device *test_device;' |7 F! V6 d: _& s. j3 Y; c
static spinlock_t lock;& W- v2 l+ Y2 W# t$ D- y, \
static int flage = 1;  E  E0 P7 R1 a7 e/ K* P& e$ C! _

2 [. ]! M' c" y#define DEAD 1
* z2 M+ p! M( n5 K* Ystatic int hello_open (struct inode *inode, struct file *filep)
9 K4 Z6 A( U  j1 b" G. |{( V* [( q# f. y3 a
    spin_lock(&lock);
6 @9 c; y, ~6 l( M7 r5 u1 [( [    if(flage !=1)
# P7 z. k6 n/ F9 d    {% _' d0 o; s& R- N: y3 e. a% R
         spin_unlock(&lock);7 c! ?+ z5 l) l9 |
         return -EBUSY;1 K- _( G; P/ t0 ~( X
    }
! k; T. U7 a$ @2 u9 h( j3 F) W    flage =0;- ?2 K. p4 O7 ]0 E6 C5 E
    #if DEAD
+ I, @1 [  e8 A8 N0 b' u/ i8 [4 m3 F    #elif. S6 r1 I* j4 k
    spin_unlock(&lock);& L: R$ k  t5 ~4 H( H
    #endif4 S' S& W7 \+ e: n2 h0 |% C
    return 0;7 @2 [1 F; ^; d2 u+ j7 a
}
* m2 \6 L0 M8 W, Jstatic int hello_release (struct inode *inode, struct file *filep)1 M5 `% R7 T$ W$ }+ R+ _9 U
{6 J. W* m& l# [7 w/ l3 r
    flage = 1;
% G" h  M9 l7 b- R1 V9 N4 t    #if DEAD. u9 q2 G- \# m2 l' T$ k9 _
    spin_unlock(&lock);, a  B1 `; z8 Q# P5 x
    #endif* i( _6 G) a( c- C7 T
    return 0;  V+ l0 k4 {0 h; k  d; n% C3 z. x
}
9 K3 r3 x) `8 i$ L4 \static struct file_operations hello_ops =
0 @( q* Q! [8 o) o4 s4 g{# ]4 w* Y4 P* |# _
    .open = hello_open,
6 i3 p$ G2 i* m7 s5 q* U$ c    .release = hello_release,- M  N1 h  I8 U+ H
};
& ^8 Q+ a# L1 W$ m1 cstatic int hello_init(void)
9 l* J  J6 t; t+ z7 O{
+ E" X  l1 k& K& U1 ^+ Y! Q    int result;
+ N/ A+ n& _9 ?% j. G1 j% F# I    int error;   
5 M) W0 ^; [9 Q2 I; r, Y    printk("hello_init \n");
2 r7 O- `3 ~: a" m- d    result = register_chrdev( major, "hello", &hello_ops);
$ Q1 j! Q  P7 T# W  @( q    if(result < 0)5 y7 C. h+ k. ?3 _5 I! a
    {9 s1 o9 N9 M, I' L' d% I
        printk("register_chrdev fail \n");
& a- p8 g- o3 k" e9 i- m& t        return result;
1 U; r) f' {8 P+ E9 f! [/ z1 o    }7 r4 _2 p+ T% K$ a2 R8 S- {; K
    devno = MKDEV(major,minor);- i; S0 O8 X  m5 g: a* }
    cls = class_create(THIS_MODULE,"helloclass");7 V2 \+ u1 d: W5 h; C$ ~% U
    if(IS_ERR(cls))
  W: n& }( X0 q+ n$ B; H    {* r- \8 }6 M% O' ?; i
        unregister_chrdev(major,"hello");
" Y4 y, b/ w/ w3 {2 R        return result;
& O1 b$ R: T! o* x    }& L* w. V" h4 e; f% L/ {4 @
    test_device = device_create(cls,NULL,devno,NULL,"test");; Y1 X' v& s( Z, i; O1 v9 M
    if(IS_ERR(test_device ))# }' {3 g  O) D/ I: x0 H
    {% `; U( A! Y5 j  a1 O/ I; V
        class_destroy(cls);
! t/ C& M4 L6 h* {! f* e        unregister_chrdev(major,"hello");7 O3 W  ]3 z! r) ~
        return result;
8 I1 }2 Y4 p2 Q2 b7 _6 C    }
9 g* v( |8 e3 k    spin_lock_init(&lock);, B7 `% _6 S2 w9 p5 W! k
    return 0;8 L' }7 t. q& W3 [  `# ?
}
, Y: a5 m" g! @: w  `3 ^static void hello_exit(void); f" A+ L9 _/ N0 k2 Z
{' M% p" Q& l' a: d& _! N9 r( g0 x/ @
    printk("hello_exit \n");
/ j, Y. n0 b! Z+ T4 `    device_destroy(cls,devno);   
( e/ _% }, _" S+ O    class_destroy(cls);
/ ^' G! L3 p+ Z$ J9 t, g/ w) E    unregister_chrdev(major,"hello");7 C6 T$ v+ v( J6 Y5 C
    return;
) ]) F- b" D, Q9 }' ]}$ [( I8 K  {# i) U
module_init(hello_init);/ w8 A+ |: W. g
module_exit(hello_exit);
  }2 A6 l: K/ W4 e* ]! W+ B' ZMODULE_LICENSE("GPL");
, }9 V2 {% p# Q1 M9 s

测试程序如下:

#include <stdio.h>
$ H3 e9 C4 X3 `0 T+ [#include <sys/types.h>+ G, b  s# d+ K* h. B! s
#include <sys/stat.h>
% j# \  f' W1 N8 K+ _1 W#include <fcntl.h>6 i5 S4 D1 U' p. v- D3 W
main()
/ u: `0 D- R5 m& @/ x; C{6 h3 ^( O' u: f1 G) g
    int fd;
6 I. T1 S3 v8 a6 ~) d% f, C( q    fd = open("/dev/test",O_RDWR);& x+ f7 N' r1 u! R/ k3 d5 R' u' G( G
    if(fd<0)) g7 c+ ?! w+ _
    {
6 D* y8 {% ]/ ~+ H% F9 V# c! G+ K        perror("open fail \n");. `1 g/ h5 r4 }' q, C
        return;
: c# E8 t4 O5 U) `- ~    }2 U% P, _! E9 z- d) Z6 T
    sleep(20);
, E  I6 r' I2 ?: q( L; i! Q    close(fd);   ' v6 x6 g) I( |+ q4 l0 U
    printf("open ok \n ");2 I; |: s7 {8 j% f
}
) m- f. T: i; ?2 X! i

测试步骤:

  • 编译加载内核
    0 b1 X2 b) v5 v7 Z4 b
make1 r; F4 W: _) r. [# z& }
insmod hello.ko2 l3 q! d. p# p  c' T+ A- a
  • 运行进程A0 P7 X1 q  m! S' I5 Z
gcc test.c -o a
) k: F, E7 b% V  E! f. f/ ~  @./a
- H* K5 z; |$ F: a4 B2 s7 M& _
  • 打开一个新的终端,运行进程B7 p% b* |6 y# k0 i
gcc test.c -o b
- o% d! D  ]/ d2 H./b
" Y) U. L0 O  n$ ~8 `0 C+ `+ z$ [

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

8 H# Y& W. T' D: ^) Z! L1 N
收藏 评论0 发布时间:2020-9-4 20:23

举报

0个回答

所属标签

相似分享

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