1,Linux裝置驅動屬於核心的一部分,Linux核心的一個模組可以以兩種方式被編譯和載入:
(1)直接編譯進Linux核心,隨同Linux啟動時載入;
(2)編譯成一個可載入和刪除的模組,使用insmod載入(modprobe和insmod命令類似,但依賴於相關的設定檔),rmmod刪除。
記憶體
在Linux核心模式下,我們不能使用使用者態的malloc()和free()函數申請和釋放記憶體。進行核心編程時,最常用的記憶體申請和釋放函數為在include/linux/kernel.h檔案中聲明的kmalloc()和kfree(),其原型為:
void *kmalloc(unsigned int len, int priority); void kfree(void *__ptr); |
kmalloc的priority參數通常設定為GFP_KERNEL,如果在中斷服務程式裡申請記憶體則要用GFP_ATOMIC參數,因為使用GFP_KERNEL參數可能會引起睡眠,不能用於非進程上下文中(在中斷中是不允許睡眠的)。
由於核心態和使用者態使用不同的記憶體定義,所以二者之間不能直接存取對方的記憶體。而應該使用Linux中的使用者和核心態記憶體互動函數(這些函數在include/asm/uaccess.h中被聲明):
unsigned long copy_from_user(void *to, const void *from, unsigned long n); unsigned long copy_to_user (void * to, void * from, unsigned long len); |
copy_from_user、copy_to_user函數返回不能被複製的位元組數,因此,如果完全複製成功,傳回值為0。
include/asm/uaccess.h中定義的put_user和get_user用於核心空間和使用者空間的單值互動(如char、int、long)。
一個簡單的gobalvar模組:
裝置"gobalvar"的驅動程式的這些函數應分別命名為gobalvar_open、gobalvar_
release、gobalvar_read、gobalvar_write、gobalvar_ioctl,因此裝置"gobalvar"的基本進入點
結構變數gobalvar_fops 賦值如下:
struct file_operations gobalvar_fops = { read: gobalvar_read, write: gobalvar_write, }; |
上述代碼中對gobalvar_fops的初始化方法並不是標準C所支援的,屬於GNU擴充文法。
完整的globalvar.c檔案原始碼如下:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <asm/uaccess.h>
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 =
{
read: globalvar_read, write: globalvar_write,
};
static int global_var = 0; //"globalvar"裝置的全域變數
static int __init globalvar_init(void)
{
int ret;
//註冊裝置驅動
ret = register_chrdev(MAJOR_NUM, "globalvar", &globalvar_fops);
if (ret)
{
printk("globalvar register failure");
}
else
{
printk("globalvar register success");
}
return ret;
}
static void __exit globalvar_exit(void)
{
int ret;
//登出裝置驅動
ret = unregister_chrdev(MAJOR_NUM, "globalvar");
if (ret)
{
printk("globalvar unregister failure");
}
else
{
printk("globalvar unregister success");
}
}
static ssize_t globalvar_read(struct file *filp, char *buf, size_t len, loff_t *off)
{
//將global_var從核心空間複製到使用者空間
if (copy_to_user(buf, &global_var, sizeof(int)))
{
return - EFAULT;
}
return sizeof(int);
}
static ssize_t globalvar_write(struct file *filp, const char *buf, size_t len, loff_t *off)
{
//將使用者空間的資料複製到核心空間的global_var
if (copy_from_user(&global_var, buf, sizeof(int)))
{
return - EFAULT;
}
return sizeof(int);
}
module_init(globalvar_init);
module_exit(globalvar_exit);
make file 內容:
obj-m += globalvar.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(pwd) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(pwd) clean
2, 並發控制
在驅動程式中,當多個線程同時訪問相同的資源時(驅動程式中的全域變數是一種典型的共用資源),可能會引發"競態",因此我們必須對共用資源進行並發控制。Linux核心中解決並發控制的最常用方法是自旋鎖與訊號量(絕大多數時候作為互斥鎖使用)。
自旋鎖與訊號量"類似而不類",類似說的是它們功能上的相似性,"不類"指代它們在本質和實現機理上完全不一樣,不屬於一類。
自旋鎖不會引起調用者睡眠,如果自旋鎖已經被別的執行單元保持,調用者就一直迴圈查看是否該自旋鎖的保持者已經釋放了鎖,"自旋"就是"在原地打轉"。而訊號量則引起調用者睡眠,它把進程從運行隊列上拖出去,除非獲得鎖。這就是它們的"不類"。
但是,無論是訊號量,還是自旋鎖,在任何時刻,最多隻能有一個保持者,即在任何時刻最多隻能有一個執行單元獲得鎖。這就是它們的"類似"。
與訊號量相關的API主要有:
定義訊號量
初始化訊號量
| void sema_init (struct semaphore *sem, int val); |
該函數初始化訊號量,並設定訊號量sem的值為val
| void init_MUTEX (struct semaphore *sem); |
該函數用於初始化一個互斥鎖,即它把訊號量sem的值設定為1,等同於sema_init (struct semaphore *sem, 1);
| void init_MUTEX_LOCKED (struct semaphore *sem); |
該函數也用於初始化一個互斥鎖,但它把訊號量sem的值設定為0,等同於sema_init (struct semaphore *sem, 0);
獲得訊號量
| void down(struct semaphore * sem); |
該函數用於獲得訊號量sem,它會導致睡眠,因此不能在中斷上下文使用;
| int down_interruptible(struct semaphore * sem); |
該函數功能與down類似,不同之處為,down不能被訊號打斷,但down_interruptible能被訊號打斷;
| int down_trylock(struct semaphore * sem); |
該函數嘗試獲得訊號量sem,如果能夠立刻獲得,它就獲得該訊號量並返回0,否則,返回非0值。它不會導致調用者睡眠,可以在中斷上下文使用。
釋放訊號量
| void up(struct semaphore * sem); |
該函數釋放訊號量sem,喚醒等待者。
與自旋鎖相關的API主要有:
定義自旋鎖
初始化自旋鎖
該宏用於動態初始化自旋鎖lock
獲得自旋鎖
該宏用於獲得自旋鎖lock,如果能夠立即獲得鎖,它就馬上返回,否則,它將自旋在那裡,直到該自旋鎖的保持者釋放;
該宏嘗試獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖並返回真,否則立即返回假,實際上不再"在原地打轉";
釋放自旋鎖
該宏釋放自旋鎖lock,它與spin_trylock或spin_lock配對使用;
3,阻塞與非阻塞實現
阻塞操作是指,在執行裝置操作時,若不能獲得資源,則進程掛起直到滿足可操作的條件再進行操作。
非阻塞操作的進程在不能進行裝置操作時,並不掛起,而是被CPU調度出進入睡眠。
阻塞,應用程式層:read or write ,核心層:等待隊列
非阻塞,應用程式層:select ,核心層:等待隊列+裝置poll函數實現
poll函數原型為
static unsigned int device_poll(struct file *filp, poll_table *wait)
該函數中最主要用到的一個API是poll_wait,其原型如下:
void poll_wait(struct file *filp, wait_queue_heat_t *queue, poll_table * wait);
poll_wait函數所做的工作是把當前進程添加到wait參數指定的等待列表(poll_table)中。