设备驱动中的并发控制-原子操作

参考:《Linux驱动开发入门与实战》   

        现代操作系统有三大特性;中断处理、多任务处理、多处理器(SMP)。这些特性导致当多个进程、线程或CPU同时访问同一个资源时,可能会发生错误。即会存在多个任务同时访问同一片内存区域,这些任务可能会相互覆盖这段内存中的数据,造成内存数据混乱。严重的话可能会导致系统崩溃。

1. 并发与竞争

        现在的 Linux 系统并发产生的原因很复杂,总结一下有下面几个主要原因:
1.1 多线程并发访问,Linux 是多任务(线程)的系统,所以多线程访问是最基本的原因。
1.2 抢占式并发访问,从 2.6 版本内核开始,Linux 内核支持抢占,也就是说调度程序可以
在任意时刻抢占正在运行的线程,从而运行其他的线程。
1.3 中断程序并发访问,硬件中断的权利很大。
1.4 SMP(多核)核间并发访问,现在 ARM 架构的多核 SOC 很常见,多核 CPU 存在核间并发访问。
        并发访问带来的问题就是竞争,如果多个线程同时操作临界区就表示存在竞争,我们在编写驱动的时候一定要注意避免并发和防止竞争访问。(所谓的临界区就是共享数据段,对于临界区必须保证一次只有一个线程访问,也就是要保证临界区是原子访问的。)

        并发控制机制有原子变量操作、自旋锁、信号量、完成量。

2. 原子变量操作

        所谓原子变量操作,就是该操作在执行完之前绝不会被任何其他任务或事件打断。原子变量操作需要硬件的支持,因此是与架构相关的,其API和原子类型的定义在内核源码的路径为:include/asm/atomic.h。他们都是使用汇编语言实现,因为C语言并不能实现这样的操作。

        原子变量操作不会只执行一半。它要么执行完毕,要么一点也不执行。原子变量操作的优点是编写简单;缺点是功能太简单,只能做计数操作。但是原子变量操作是其他同步手段的基石。

在Linux中,原子变量的定义如下:

typedef struct{

volatile int counter;

} atomic_t;

        关键字volatile用来指示GCC不要对该类型做数据优化,所以对这个变量counter的访问都是基于内存的,不要将其缓冲到寄存器中。存储到寄存器中,可能导致内存中的数据已经改变,而寄存器中的数据没有改变。

在Linux中,定义了两种原子变量操作的方法,一种是原子整形操作,另一种是原子位操作。

2.1 原子整形操作

        当需要共享的资源只是一个简单的整形数值。例如在驱动程序中,需要包含一个计数器count。这个计数器表示有多少个应用程序打开了设备所对应的设备文件。在设备驱动程序的open()函数种将count加1,在close()函数中将count减1。如果有多个应用程序同时打开或关闭设备文件,那么就可能导致count多加或少加。为了避免这个问题,内核提供了原子整形变量atomic_t。该变量定义如下:

typedef struct{

volatile int counter;

} atomic_t;

定义count计数器范例如下:

atomic_t count;

        这句代码定义了一个atomic_t类型的count变量,atomic_t变量只能通过linux内核中定义的专用函数来操作,不能在变量上直接加1或者减1。

2.1.1  定义atomic_t变量

#define ATOMIC_INIT(i) { {i} }

atomic_t count = ATOMIC_INIT(0);

第一行宏 ATOMIC_INIT(i) 用于给atomic_t类型的变量初始化,宏的参数i就是初始化的值。

第二行对atomic_t类型变量count初始化。

        因为atomic_t类型变量是一个结构体类型,所以对其进行定义和初始化应该用结构体定义和初始化的方法。

​​​​​​​2.1.2 设置atomic_t变量的值

atomic_set(v,t)宏用来设置v变量的值为i。其定义如下所示:

#define atomic_set(v, i)     ( ( (v)->counter ) == i )

​​​​​​​​​​​​​​2.1.3 读atomic_t变量的值

atomic_read(v)宏用来读v变量的值。其定义如下所示:

#define atomic_read(v)     ( (v)->counter ) )

该宏对原子类型变量进行原子读操作,它返回原子类型变量v的值。

​​​​​​​​​​​​​​​​​​​​​​​​​​​​2.1.4 原子变量的加减法

atomic_add()函数用来将第一个参数i的值加到第二个参数v中,并返回一个void值。

static inline void atomic_add(int i, volatile atomic_t *v)

与atomic_sub()函数功能相反的函数是atomic_sub()函数,该函数从原子变量v中减去i的值。

static inline void atomic_sub(int i, volatile atomic_t *v)

​​​​​​​​​​​​​​​​​​​​​​​​​​​​2.1.5 原子变量的自加自减

atomic_inc()函数用来将v指向的变量加1,并返回一个void值。

static inline void atomic_inc( volatile atomic_t *v)

atomic_dec()函数用来将v指向的变量减1,并返回一个void值。

static inline void atomic_dec( volatile atomic_t *v)

​​​​​​​​​​​​​​​​​​​​​​​​​​​​2.1.6 加减测试

atomic_inc_and_test()函数用来将v指向的变量加1,如果结果是0,则字节返回真;如果是非0,则返回假。

static inline int atomic_inc_and_test( volatile atomic_t *v)

atomic_dec_and_test()函数用来将v指向的变量减1,如果结果是0,则字节返回真;如果是非0,则返回假。

static inline int atomic_dec_and_test( volatile atomic_t *v)

        综上所述,atomic_t类型的变量必须使用上面介绍的函数来访问,如果试图将原子变量看作整形变量来使用,则会出现编译错误。

​​​​​​​​​​​​​​2.2 原子位操作

        原子位操作是根据数据的每一位单独进行操作。这些函数的原型如下所示:

static inline void set_bit(intnr, volatile unsigned long *addr)

static inline void clear_bit(intnr, volatile unsigned long *addr)

static inline void change_bit(intnr, volatile unsigned long *addr)

static inline void test_and_set_bit(intnr, volatile unsigned long *addr)

static inline void test_and_clear_bit(intnr, volatile unsigned long *addr)

static inline void test_and_change_bit(intnr, volatile unsigned long *addr)

需要注意的是,原子位操作和原子整数操作是不同的。原子位操作不需要专门定义一个类似atomic_t类型的变量,只需要一个普通的变量指针就可以。

set_bit函数将addr指向的变量的第nr位设置为1;

clear_bit函数将addr指向的变量的第nr位设置为0;

change_bit函数将addr指向的变量的第nr位设置为相反的数;

test_and_set_bit函数将addr指向的变量的第nr位设置为1,并返回没有修改之前的值;

test_and_clear_bit函数将addr指向的变量的第nr位设置为0,并返回没有修改之前的值;

test_and_change_bit函数将addr指向的变量的第nr位设置为相反的数,并返回没有修改之前的值;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
优秀的,完整的BIOS 代码 page ,132 title . PROCESSOR_TIMER_PARITY_REFRESH_NMI TEST ;*****************************************************************; ;*****************************************************************; ;** **; ;** (C)Copyright 1985-1996, American Megatrends Inc. **; ;** **; ;** All Rights Reserved. **; ;** **; ;** 6145-F, Northbelt Parkway, Norcross, **; ;** **; ;** Georgia - 30071, USA. Phone-(770)-246-8600. **; ;** **; ;*****************************************************************; ;*****************************************************************; ;---------------------------------------; include mbiosequ.equ ; bios global constants include mbiosmac.mac ; bios coding macro definition include cf.equ ;---------------------------------------; extrn check_point_no_stack:near extrn check_point_stack:near public bios_suru public shutdown_0 public shutdown_77 public shutdown_88 extrn shutdown_1:near ; shutdown after memory error extrn shutdown_2:near ; v_mode exception intr error extrn _shutdown_33:near ; shutdown after memory test extrn shutdown_4:near ; boot loader request shutdown extrn shutdown_5:near ; user defined shutdown routine extrn _shutdown_66:near ; shutdown during memory test to display memory size extrn shutdown_7:near ; unused, (chipset memory detection) extrn shutdown_8:near ; unused, (soft reset) extrn shutdown_9:near ; shutdown after block move extrn shutdown_a:near ; user defined shutdown routine extrn init_8259_80287:near ; shut 4/5, init 8259, 80287 extrn disable_video:near extrn clear_64k_memory:near extrn int_isr:near extrn _power_on_delay:byte extrn _software_delay:byte extrn ram_segment:word extrn flush_all_cache:near extrn power_on_init:near public power_on_init_end extrn hreset_clear:near public hreset_clear_end extrn sreset_clear:near public sreset_clear_end extern StopUsbHostController(dummy_ret):near extrn dummy_ret:near extrn disable_all_cache:near extrn shutdown_init:near public shutdown_init_end extrn decompress_post_init:near ;---------------------------------------; ; C O D E S E G M E N T ; ;---------------------------------------; cgroup group _text _text segment word public 'CODE' assume cs:cgroup .486p ;---------------------------------------; public _BIOS_STARTS _BIOS_STARTS label byte ; marks start of module ;---------------------------------------; ; FLAGS TEST (SF,ZF,PF,CF) ; ; ;---;---;---;---;---;---;---;---; ; ; ; S ; Z ; x ; A ; x ; P ; x ; C ; ; ; ;---;---;---;---;---;---;---;---; ; ; HARD RESET OR SHUTDOWN RESET ; ;---------------------------------------; ; SHUTDOWN PROCESSING ; ;---------------------------------------; bios_suru: cli ; test under CLI mode cld ; ensure direction mov ax,cs mov ss,ax ; $$$CORE0036+ >>> extern wake_up(wake_up_end):near public wake_up_end jmp wake_up ; check for wakeup wake_up_end: ; control will come here only if wake up is not needed ; $$$CORE0036+ <<< jmp power_on_init power_on_init_end: in al,kb_stat_port ; if sys_flag bit is set test al,00000100b ; then soft reset else power on jnz shut_5 ; not power-on ;---------------------------------------; ; VANILLA MEMORY PATCH ; ;---------------------------------------; ifdef VANILLA_BIOS ;---------------------------------------; ; save CPUID in cmos 35h(DL), 36h(DH) extrn cmos_data_out:near extrn _refresh_value:byte mov ebp,edx mov al,0b5h mov ah,dl ret_sp cmos_data_out mov al,0b6h mov ah,dh ret_sp cmos_data_out ; start memory refresh.. mov al,00h ; initialise DMA-PAGE reg. out 8fh,al ; (used in MEMORY REFRESH) io_delay mov al,01010100b ; start CH_1 (REFRESH) out 43h,al ; one byte count used io_delay mov al,cgroup:_refresh_value; low byte count out 41h,al mov cx,100h ; 400h..01/11/95 xor di,di mov es,di wpulse1: stosb loop wpulse1 ;---------------------------------------; extrn vanilla_patch_offset:near cmp cgroup:word ptr vanilla_patch_offset,0ffffh jz ret_off ; no routine mov sp,offset cgroup:sp_ret_off jmp vanilla_patch_offset-1 even sp_ret_off: dw offset cgroup:ret_off dw 0f000h ret_off: endif ;---------------------------------------; jmp hreset_clear ; hard reset init (if any) ;;;;hreset_clear_end: ;;;; jmp shutdown_0x ; hard reset, goto regs. test shut_5: jmp sreset_clear ; GA20 disable and other.... sreset_clear_end: mov al,8fh ; shutdown address out cmos_addr_port,al ; mask NMI & select shut byte jcxz short $+2 ; i/o delay jcxz short $+2 ; i/o delay in al,cmos_data_port ; read shutdown code mov ah,00 mov si,ax ; save in (SI) mov al,8fh ; shutdown address jcxz short $+2 ; i/o delay out cmos_addr_port,al mov al,00 ; clear shutdown byte jcxz short $+2 ; i/o delay jcxz short $+2 ; i/o delay out cmos_data_port,al mov ax,cs mov ss,ax mov ax,si ; restore shutdown code cmp al,04h ; for BOOT loader shutdown jz shut_1 ; & for USER defined shutdown cmp al,05h ; initialize 8259 (#1, #2) jz shut_1 ; and (DS), (SS), (SP), STI cmp al,0ah ; if shut code > 10 jbe shut_2 ; bypass intr. init jmp short shutdown_0 ; then hard reset ;---------------------------------------; ; INIT 8259 for SHUTDOWN 04, 05 ; ;---------------------------------------; shut_1: mov bx,level_2_int*256+level_1_int ret_sp init_8259_80287 ; shut 4/5, init 8259, 80287 ;---------------------------------------; ; INTERNAL CACHE IS ALWAYS ON ; ;---------------------------------------; shut_2: jmp shutdown_init shutdown_init_end: mov ax,40h ; global data segment (ah) = 0 mov ds,ax ; setup (DS) mov al,00h ; global extra segment (ah) = 0 mov es,ax ; setup (ES) mov al,30h ; global stack segment (ah) = 0 mov ss,ax ; setup (SS) mov sp,0100h ; setup (SP) shl si,1 ; prepare index jmp [si+cgroup:shut_jmp_tbl]; brunch with intr. disabled ;---------------------------------------; ; SHUTDOWN JMP TABLE ; ;---------------------------------------; even shut_jmp_tbl label word dw offset cgroup:shutdown_0; hard reset dw offset cgroup:shutdown_1; used for block move internal shutdown dw offset cgroup:shutdown_2; v_mode exception intr error dw offset cgroup:_shutdown_33; shutdown after memory test dw offset cgroup:shutdown_4; shutdown for boot loader dw offset cgroup:shutdown_5; shutdown (with intr. init) dw offset cgroup:_shutdown_66; shutdown during memory test to display memory size dw offset cgroup:shutdown_7; unused, (chipset memory detection) dw offset cgroup:shutdown_8; unused, (soft reset) dw offset cgroup:shutdown_9; shutdown after block move dw offset cgroup:shutdown_a; shutdown (w/o intr. init) ;---------------------------------------; hreset_clear_end: shutdown_0x: ; hard reset mov al,8dh out cmos_addr_port,al ; NMI OFF ;---------------------------------------; shutdown_0: ; hard reset shutdown_77: ; unused shutdown_88: ; unused check_point_si 03h ; ======== 03 mov ax,cs mov ss,ax xor bp,bp mov ds,bp ; set (DS) = 0 mov es,bp ; set (ES) = 0 jmp_di disable_video in al,kb_stat_port ; if sys_flag bit is set test al,00000100b ; then soft reset else power on jz not_cnt_alt_del ; power on cmp ds:word ptr [0472h],1234h jnz cnt_alt_del yes_cnt_alt_del: or bp,soft_reset_bit ; msb used for soft reset jmp short cnt_alt_del not_cnt_alt_del: cmp ds:word ptr [0472h],1234h jz yes_cnt_alt_del or bp,power_on_bit cnt_alt_del: ;;;; ret_sp clear_64k_memory ; clear segment 0 mov ax,30h mov ss,ax ; set stack mov sp,100h check_point 05h ; ======== 05 ;; call StopUsbHostController ; disable USB host controller ;; call disable_all_cache ; disable all cache call disable_all_cache ; disable all cache call StopUsbHostController ; disable USB host controller mov ax,cs mov ss,ax ret_sp clear_64k_memory ; clear segment 0 mov ax,30h mov ss,ax ; set stack mov sp,100h check_point 06h ; ======== 06 call decompress_post_init extrn _bios_to_rm:near jmp _bios_to_rm ;---------------------------------------; ;*****************************************************************; ;*****************************************************************; ;** **; ;** (C)Copyright 1985-1996, American Megatrends Inc. **; ;** **; ;** All Rights Reserved. **; ;** **; ;** 6145-F, Northbelt Parkway, Norcross, **; ;** **; ;** Georgia - 30071, USA. Phone-(770)-246-8600. **; ;** **; ;*****************************************************************; ;*****************************************************************; ;-----------------------------------------------------------------------; ; COPY_CONTROL_TO_RAM ; ; this routine copies 64k code to ram segment and give control to ram. ; ; input : ; ; none ; ; stack available ; ; output: ; ; none ; ; register destroyed..ALL except DS, ES, BP ; ;-----------------------------------------------------------------------; extrn copy_64k_memory:near public copy_control_to_ram copy_control_to_ram proc near push es push ds mov es,cgroup:ram_segment push cs pop ds ; source segment call copy_64k_memory ; transfer control to ram segment.. push es ; ram_segment push offset cgroup:xxx retf xxx: pop ds pop es xxx_exit: ret copy_control_to_ram endp ;-----------------------------------------------------------------------; ; COPY_TO_SHADOW ; ;-----------------------------------------------------------------------; ; this routine copies the code to asked shadow. ; ; input : ; ; ES:DI destn seg:off ; ; DS:SI source segment:offset ; ; CX #of bytes to move ; ; 0000 = 64k copy ; ; stack available ; ; output: ; ; none ; ; register destroyed..ALL except DS, ES, BP ; ;-----------------------------------------------------------------------; extrn move_bytes:near extrn enable_shadow_write:near extrn disable_shadow_write:near public copy_to_shadow copy_to_shadow proc near pushf cli ; disable interrupt call enable_shadow_write ; enable write to shadow call move_bytes ; move bytes call flush_all_cache ; flush all cache call disable_shadow_write ; enable write to shadow popf ret copy_to_shadow endp ;-----------------------------------------------------------------------; extrn e000_read_rom_write_x:near extrn e000_read_ram_write_rom:near extrn e000_read_x_write_ram:near extrn f000_read_rom_write_x:near extrn f000_read_ram_write_rom:near extrn f000_read_x_write_ram:near public do_f000_read_rom_write_x public do_f000_read_ram_write_rom public do_f000_read_x_write_ram public do_e000_read_rom_write_x public do_e000_read_ram_write_rom public do_e000_read_x_write_ram ;---------------------------------------; ifdef VANILLA_BIOS do_f000_read_rom_write_x: call f000_read_rom_write_x mov al,15 ; offset to table of fn# jmp short vanilla_patch do_f000_read_ram_write_rom: call f000_read_ram_write_rom mov al,18 ; offset to table of fn# jmp short vanilla_patch do_f000_read_x_write_ram: call f000_read_x_write_ram mov al,21 ; offset to table of fn# jmp short vanilla_patch do_e000_read_rom_write_x: call e000_read_rom_write_x mov al,24 ; offset to table of fn# jmp short vanilla_patch do_e000_read_ram_write_rom: call e000_read_ram_write_rom mov al,27 ; offset to table of fn# jmp short vanilla_patch do_e000_read_x_write_ram: call e000_read_x_write_ram mov al,30 ; offset to table of fn# vanilla_patch: push bx mov bx,cgroup:word ptr vanilla_patch_offset inc bx jz dop_00 ; no routine dec bx cbw ; AX = offset to jmp table add bx,ax push cs push offset cgroup:dop_00 push cs push bx retf dop_00: pop bx ret ;---------------------------------------; else ; NORMAL BIOS CODE do_f000_read_rom_write_x: jmp f000_read_rom_write_x do_f000_read_ram_write_rom: jmp f000_read_ram_write_rom do_f000_read_x_write_ram: jmp f000_read_x_write_ram do_e000_read_rom_write_x: jmp e000_read_rom_write_x do_e000_read_ram_write_rom: jmp e000_read_ram_write_rom do_e000_read_x_write_ram: jmp e000_read_x_write_ram endif ;-----------------------------------------------------------------------; ;*****************************************************************; ;*****************************************************************; ;** **; ;** (C)Copyright 1985-1996, American Megatrends Inc. **; ;** **; ;** All Rights Reserved. **; ;** **; ;** 6145-F, Northbelt Parkway, Norcross, **; ;** **; ;** Georgia - 30071, USA. Phone-(770)-246-8600. **; ;** **; ;*****************************************************************; ;*****************************************************************; ;---------------------------------------; public _BIOS_ENDS _BIOS_ENDS label byte ; marks end of module ;---------------------------------------; _text ends end
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值