1. 程式人生 > >Linux核心模組程式設計-字元裝置驅動

Linux核心模組程式設計-字元裝置驅動

裝置驅動簡介

裝置被大概的分為兩類: 字元裝置和塊裝置。

  • 字元裝置
    提供連續的資料流,應用程式可以順序讀取,通常不支援隨機存取。相反,此類裝置支援按位元組/字元來讀寫資料。舉例來說,鍵盤、串列埠、調變解調器都是典型的字元裝置。

  • 塊裝置
    應用程式可以隨機訪問裝置資料,程式可自行確定讀取資料的位置。硬碟、軟盤、CD-ROM驅動器和快閃記憶體都是典型的塊裝置,應用程式可以定址磁碟上的任何位置,並由此讀取資料。此外,資料的讀寫只能以塊(通常是512B)的倍數進行。與字元裝置不同,塊裝置並不支援基於字元的定址。

這兩種型別的裝置的根本區別在於它們是否可以被隨機訪問。字元裝置只能順序讀取,塊裝置可以隨機讀取。其它的一些區別:

  • 字元裝置只能以位元組為最小單位訪問,而塊裝置以塊為單位訪問,例如512位元組,1024位元組等
  • 塊裝置可以隨機訪問,但是字元裝置不可以
  • 字元和塊沒有訪問量大小的限制,塊也可以以位元組為單位來訪問

對於裝置來說,linux抽象為一個個檔案,放在/dev目錄下,每個檔案都有一定的屬性其中比較重要的有主裝置號和次裝置號兩個。主裝置號標明瞭裝置的型別,次裝置號表示同類型裝置的不同裝置

[root@localhost blog]# ls -al /dev/sda
brw-rw----. 1 root disk 8, 0 Apr 22 22:49 /dev/sda
8是主裝置號表示這是一個磁碟,0是次裝置號區別相同型別裝置的不同裝置

開始編寫字元裝置驅動

註冊和撤銷裝置

通過註冊裝置驅動模組,可以將一組操作裝置的函式和某一裝置進行關聯起來。
註冊一個裝置:

linux/fs.h 中的函式 register_chrdev 
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
其中major是主裝置號,name是裝置的名字,fops則是該裝置對應的一些列操作函式,成功返回主裝置號
major如果為0的話,系統會給我們隨機分配主裝置號

問題是主裝置號怎麼去定義,難道可以自己隨便填嗎?如果大家都這樣,那麼裝置好像就亂了要申請一個沒有使用的裝置號可以去檢視文件:Documentation/devices.txt
撤銷一個裝置:

int unregister_chrdev(unsigned int major, const char *name);

在linux中把對裝置的操作,抽象成對檔案的操作,寫裝置驅動程式,其實本質是去填充file_operations這個結構體

裝置操作

實現一個最基本的裝置驅動程式的話,那麼至少應該實現下面四個操作裝置的函式開啟一個裝置,向裝置寫入資料,從裝置讀取資料,關閉裝置等

static struct file_operations fops = {
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release
};

開啟裝置的實現

static int Device_Open = 0; //表明裝置狀態,是開啟還是關閉
所有開啟裝置的函式原型都是這樣,一個裝置的inode,還有一個與裝置檔案對應的file結構體
static int device_open(struct inode *inode,struct file *file)
{
    static int counter = 0;  //記錄裝置開啟的次數
    if(Device_Open) 
        return -EBUSY;
    Device_Open++; 
    sprintf(msg,"I already told you %d times Hello world\n",counter++);
    msg_Ptr = msg;
    try_module_get(THIS_MODULE);  //增加核心模組的引用的計數
    return SUCCESS;
}

釋放裝置的實現

//釋放裝置
static int device_release(struct inode *inode,struct file *file)
{
    Device_Open--;   //更改裝置開啟狀態
    module_put(THIS_MODULE);  //減少核心模組的引用計數
    return 0;
}

讀取裝置的實現

核心空間和使用者空間的資料拷貝使用copy_to_user和copy_from_user兩個函式實現 
//讀檔案,把核心空間的資料拷貝到buffer上
static ssize_t device_read(struct file *filp,char *buffer,size_t length,loff_t *offset)
{
    if(*msg_Ptr == 0) 
        return 0;
    return copy_to_user(buffer,msg_Ptr,length); //把msg_Ptr指向的內容拷貝到buffer中
}

寫入裝置的實現

//寫檔案,這裡佔時還沒有去實現這個功能
static ssize_t device_write(struct file *filp,const char *buff,size_t len,loff_t *off)
{
    printk("<1> Sorry this operation isn't supported\n");
    return -EINVAL;
}

到此為止,寫一個裝置驅動所具備的的要素都完成了。那麼最後是寫成一個核心模組的形式

核心模組的初始化和登出

#define SUCCESS 0
#define DEVICE_NAME "chardev"
#define BUF_LEN 80

static int Major;
static int Device_Open = 0;
static char msg[BUF_LEN];
static char *msg_Ptr;

//核心模組初始化
int init_module(void)
{
    //註冊裝置驅動,隨機產生主裝置號
    Major = register_chrdev(0,DEVICE_NAME,&fops);
    if(Major < 0) {
        printk("Registering the character device failed with %d\n",Major);
    return Major;
    }
    printk("<1> I was assigned major number %d ",Major);
    printk("<1> the drive,create a dev file");
    printk("<1> mknod /dev/hello c %d 0.\n",Major);
    printk("<1> I was assigned major number %d ",Major);
    printk("<1> the device file\n");
    printk("<1> Remove the file device and module when done\n");
    return 0;
}
void cleanup_module(void)
{
    unregister_chrdev(Major,DEVICE_NAME);
}

到此為止一個簡單的裝置驅動就完成了,剩下的時間是要測試了,

核心模組測試

插入核心模組,使用dmesg檢視系統隨機分配的主裝置號
insmod chardev.ko

dmesg檢視核心輸出日誌
 I was assigned major number 247 
 the drive,create a dev file
 mknod /dev/hello c 247 0.
 I was assigned major number 247 
 the device file
 Remove the file device and module when done

根據輸出的主裝置號,建立裝置
mknod /dev/hello c 247 0

最後你就可以使用使用者態程式去open這個裝置和read/write這個裝置了。

使用者態程式進行測試

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
int main()
{
    char buf[4096] = {0};
    int fd = open("/dev/hello",O_RDWR);
    int ret = read(fd,buf,sizeof(buf));
    buf[ret] = '\0';
    printf("%s\n",buf);
}
輸入結果:
I already told you 1 times Hello world