深入淺出Linux裝置驅動之字元裝置驅動程式

2021-04-13 13:16:45 字數 4712 閱讀 9125

linux下的裝置驅動程式被組織為一組完成不同任務的函式的集合,通過這些函式使得windows的裝置操作猶如檔案一般。在應用程式看來,硬體裝置只是乙個裝置檔案,應用程式可以象操作普通檔案一樣對硬體裝置進行操作,如open ()、close ()、read ()、write () 等。

linux主要將裝置分為二類:字元裝置和塊裝置。字元裝置是指裝置傳送和接收資料以字元的形式進行;而塊裝置則以整個資料緩衝區的形式進行。字元裝置的驅動相對比較簡單。

下面我們來假設乙個非常簡單的虛擬字元裝置:這個裝置中只有乙個4個位元組的全域性變數int global_var,而這個裝置的名字叫做"gobalvar"。對"gobalvar"裝置的讀寫等操作即是對其中全域性變數global_var的操作。

驅動程式是核心的一部分,因此我們需要給其新增模組初始化函式,該函式用來完成對所控裝置的初始化工作,並呼叫register_chrdev() 函式註冊字元裝置:

static int __init gobalvar_init(void)

else}

其中,register_chrdev函式中的引數major_num為主裝置號,"gobalvar"為裝置名,gobalvar_fops為包含基本函式入口點的結構體,型別為file_operations。當gobalvar模組被載入時,gobalvar_init被執行,它將呼叫核心函式register_chrdev,把驅動程式的基本入口點指標存放在核心的字元裝置位址表中,在使用者程序對該裝置執行系統呼叫時提供入口位址。

與模組初始化函式對應的就是模組解除安裝函式,需要呼叫register_chrdev()的"反函式" unregister_chrdev():

static void __exit gobalvar_exit(void)

else}

隨著核心不斷增加新的功能,file_operations結構體已逐漸變得越來越大,但是大多數的驅動程式只是利用了其中的一部分。對於字元裝置來說,要提供的主要入口有:open ()、release ()、read ()、write ()、ioctl ()、llseek()、poll()等。

open()函式 對裝置特殊檔案進行open()系統呼叫時,將呼叫驅動程式的open () 函式:

int (*open)(struct inode * ,struct file *);

其中引數inode為裝置特殊檔案的inode (索引結點) 結構的指標,引數file是指向這一裝置的檔案結構的指標。open()的主要任務是確定硬體處在就緒狀態、驗證次裝置號的合法性(次裝置號可以用minor(inode-> i - rdev) 取得)、控制使用裝置的程序數、根據執**況返回狀態碼(0表示成功,負數表示存在錯誤) 等;

release()函式 當最後乙個開啟裝置的使用者程序執行close ()系統呼叫時,核心將呼叫驅動程式的release () 函式:

void (*release) (struct inode * ,struct file *) ;

release 函式的主要任務是清理未結束的輸入/輸出操作、釋放資源、使用者自定義排他標誌的復位等。

read()函式 當對裝置特殊檔案進行read() 系統呼叫時,將呼叫驅動程式read() 函式:

ssize_t (*read) (struct file *, char *, size_t, loff_t *);

用來從裝置中讀取資料。當該函式指標被賦為null 值時,將導致read 系統呼叫出錯並返回-einval("invalid argument,非法引數")。函式返回非負值表示成功讀取的位元組數(返回值為"signed size"資料型別,通常就是目標平台上的固有整數型別)。

globalvar_read函式中核心空間與使用者空間的記憶體互動需要借助第2節所介紹的函式:

static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)

write( ) 函式 當裝置特殊檔案進行write () 系統呼叫時,將呼叫驅動程式的write () 函式:

ssize_t (*write) (struct file *, const char *, size_t, loff_t *);

向裝置傳送資料。如果沒有這個函式,write 系統呼叫會向呼叫程式返回乙個-einval。如果返回值非負,則表示成功寫入的位元組數。

globalvar_write函式中核心空間與使用者空間的記憶體互動需要借助第2節所介紹的函式:

static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)

ioctl() 函式 該函式是特殊的控制函式,可以通過它向裝置傳遞控制資訊或從裝置取得狀態資訊,函式原型為:

int (*ioctl) (struct inode * ,struct file * ,unsigned int ,unsigned long);

unsigned int引數為裝置驅動程式要執行的命令的**,由使用者自定義,unsigned long引數為相應的命令提供引數,型別可以是整型、指標等。如果裝置不提供ioctl 入口點,則對於任何核心未預先定義的請求,ioctl 系統呼叫將返回錯誤(-enotty,"no such ioctl fordevice,該裝置無此ioctl 命令")。如果該裝置方法返回乙個非負值,那麼該值會被返回給呼叫程式以表示呼叫成功。

llseek()函式 該函式用來修改檔案的當前讀寫位置,並將新位置作為(正的)返回值返回,原型為:

loff_t (*llseek) (struct file *, loff_t, int);

poll()函式 poll 方法是poll 和select 這兩個系統呼叫的後端實現,用來查詢裝置是否可讀或可寫,或是否處於某種特殊狀態,原型為:

unsigned int (*poll) (struct file *, struct poll_table_struct *);

我們將在"裝置的阻塞與非阻塞操作"一節對該函式進行更深入的介紹。

裝置"gobalvar"的驅動程式的這些函式應分別命名為gobalvar_open、gobalvar_ release、gobalvar_read、gobalvar_write、gobalvar_ioctl,因此裝置"gobalvar"的基本入口點結構變數gobalvar_fops 賦值如下:

struct file_operations gobalvar_fops = ;

上述**中對gobalvar_fops的初始化方法並不是標準c所支援的,屬於gnu擴充套件語法。

完整的globalvar.c檔案源**如下:

#include

#include

#include

#include

module_license("gpl");

#define major_num 254 //主裝置號

static ssize_t globalvar_read(struct file *, char *, size_t, loff_t*);

static ssize_t globalvar_write(struct file *, const char *, size_t, loff_t*);

//初始化字元裝置驅動的file_operations結構體

struct file_operations globalvar_fops =

;static int global_var = 0; //"globalvar"裝置的全域性變數

static int __init globalvar_init(void)

else

return ret;

}static void __exit globalvar_exit(void)

else

}static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)

return sizeof(int);

}static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)

return sizeof(int);

}module_init(globalvar_init);

module_exit(globalvar_exit);

執行:gcc -d__kernel__ -dmodule -dlinux -i /usr/local/src/linux2.4/include -c -o globalvar.o globalvar.c

編譯**,執行:

inmod globalvar.o

載入globalvar模組,再執行:

cat /proc/devices

發現其中多出了"254 globalvar"一行,如下圖:

接著我們可以執行:

mknod /dev/globalvar c 254 0

建立裝置節點,使用者程序通過/dev/globalvar這個路徑就可以訪問到這個全域性變數虛擬裝置了。我們寫乙個使用者態的程式globalvartest.c來驗證上述裝置:

#include

#include

#include

#include

main()

else}

編譯上述檔案:

gcc -o globalvartest.o globalvartest.c 執行

./globalvartest.o

可以發現"globalvar"裝置可以正確的讀寫。

深入淺出Linux裝置驅動之併發控制

在驅動程式中,當多個執行緒同時訪問相同的資源時 驅動程式中的全域性變數是一種典型的共享資源 可能會引發 競態 因此我們必須對共享資源進行併發控制。linux核心中解決併發控制的最常用方法是自旋鎖與訊號量 絕大多數時候作為互斥鎖使用 自旋鎖與訊號量 類似而不類 類似說的是它們功能上的相似性,不類 指代...

深入淺出 Linux裝置驅動中斷處理介紹

深入淺出 linux裝置驅動中斷處理介紹 與linux裝置驅動中中斷處理相關的首先是申請與釋放irq的api request irq 和free irq request irq 的原型為 int request irq unsigned int irq,void handler int irq,vo...

深入淺出 Linux裝置驅動非同步通知介紹

結合阻塞與非阻塞訪問 poll函式可以較好地解決裝置的讀寫,但是如果有了非同步通知就更方便了。非同步通知的意思是 一旦裝置就緒,則主動通知應用程式,這樣應用程式根本就不需要查詢裝置狀態,這一點非常類似於硬體上 中斷 地概念,比較準確的稱謂是 訊號驅動 sigio 的非同步i o 我們先來看乙個使用訊...