Linux裝置驅動程式設計要點

來源:互聯網
上載者:User
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主要有:

  定義訊號量

struct semaphore sem;

  初始化訊號量

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主要有:

  定義自旋鎖

spinlock_t spin;

  初始化自旋鎖

spin_lock_init(lock)

  該宏用於動態初始化自旋鎖lock

  獲得自旋鎖

spin_lock(lock)

  該宏用於獲得自旋鎖lock,如果能夠立即獲得鎖,它就馬上返回,否則,它將自旋在那裡,直到該自旋鎖的保持者釋放;

spin_trylock(lock)

  該宏嘗試獲得自旋鎖lock,如果能立即獲得鎖,它獲得鎖並返回真,否則立即返回假,實際上不再"在原地打轉";

  釋放自旋鎖

spin_unlock(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)中。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.