目的:寫一個統(tǒng)計單詞個數(shù)的簡單驅(qū)動。
1、驅(qū)動的概念
1.1、什么是驅(qū)動
無操作系統(tǒng)時函數(shù)可以由工程師自己定義。
有操作系統(tǒng)時,把單一的“驅(qū)使硬件設(shè)備行動”變成了操作系統(tǒng)內(nèi)與硬件交互的模塊,它對外呈現(xiàn)為操作系統(tǒng)的 API。
對設(shè)備驅(qū)動最通俗的解釋就是“驅(qū)使硬件設(shè)備行動”。設(shè)備驅(qū)動充當了硬件和應(yīng)用軟件之間的紐帶,應(yīng)用軟件時只需要調(diào)用系統(tǒng)軟件的應(yīng)用編程接口( API)就可讓硬件去完成要求的工作。
1.2 為什么要有驅(qū)動
任何一個計算機系統(tǒng)的運轉(zhuǎn)都是系統(tǒng)中軟硬件共同努力的結(jié)果,但是軟硬件工程師不想涉及對方的領(lǐng)域,因此這個中間的連接需要驅(qū)動工程師來解決。
1.3 驅(qū)動所在位置
linux設(shè)備驅(qū)動是以內(nèi)核模塊的形式出現(xiàn)。模塊的概念,可以不編入內(nèi)核鏡像,在使用的時候動態(tài)的加入到內(nèi)核中,模塊被加載后和其他內(nèi)核完全一樣。


2、linux字符設(shè)備驅(qū)動結(jié)構(gòu)
2.1 重要的兩個結(jié)構(gòu)體
2.1.1 cdev結(jié)構(gòu)體
linux內(nèi)核中,使用cdev(character dev)結(jié)構(gòu)體描述一個字符設(shè)備,結(jié)構(gòu)體如下:

-
dev_t:定義了設(shè)備號,為32位,12位為主設(shè)備號,20位為次設(shè)備號。
使用MAJOR(dev_t dev)、MINOR(dev_t dev)可以獲得,使用MKDEV(int major, int minor)可以通過主設(shè)備號和次設(shè)備號生成dev_t:
2.1.2 file_operations 結(jié)構(gòu)體
file_operations:定義了字符設(shè)備驅(qū)動提供給VFS的接口函數(shù),是 字符設(shè)備驅(qū)動程序設(shè)計的主題內(nèi)容,會在應(yīng)用程序進行l(wèi)inus的open/read()等系統(tǒng)調(diào)用時最終被內(nèi)核調(diào)用。
struct file_operations {
**struct module *owner;**
//指向module,一般初始化為THIS_MODULE
loff_t (*llseek) (struct file *, loff_t, int);
//修改一個文件的當前讀寫位置,并將新位置返回;指針參數(shù)filp為進行讀取信息的目標文件結(jié)構(gòu)體指針,
//loff_t用來維護當前讀寫位置,代表用戶空間的文件光標移動數(shù)量值
//int 代表移動光標的參考位置,有3種(即當前光標、文件開頭、文件結(jié)尾)
//移動設(shè)備的文件指針,然后讀/寫接口就可以對移動后的位置進行讀取功能,而不是每次讀/寫都只能從0開始一次讀取全部數(shù)據(jù)。llseek函數(shù)改變了filp->f_pos(position),返回新的位置
//實現(xiàn)的功能是修改驅(qū)動中write/read函數(shù)的文件指針。
**ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);**
// 從設(shè)備中讀取數(shù)據(jù),成功時返回讀取字節(jié)數(shù)
//PS:這里是_ _user
**ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);**
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
//對文件描述符對應(yīng)的設(shè)備進行異步讀操作。
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
//對于設(shè)備文件這個成員應(yīng)當為 NULL; 它用來讀取目錄, 并且僅對文件系統(tǒng)有用.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
//返回設(shè)備資源的可獲取狀態(tài),詢問是否可被非阻塞地立即讀寫。
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
// 提供設(shè)備相關(guān)控制命令的實現(xiàn)。
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
//
int (*mmap) (struct file *, struct vm_area_struct *);
// 將設(shè)備內(nèi)存映射到進程的虛擬地址空間,對幀緩沖等設(shè)備特別有意義,
//幀緩沖被映射到用戶空間后,應(yīng)用程序可以直接訪問無需在內(nèi)核和應(yīng)用間進行內(nèi)存復(fù)制。
//幀緩沖是Linux為顯示設(shè)備提供的一個接口,幀緩存的每一存儲單元對應(yīng)屏幕上的一個像素,整個幀緩存對應(yīng)一幀圖像。
** int (*open) (struct inode *, struct file *);**
//打開設(shè)備驅(qū)動
//inode 為文件節(jié)點,這個節(jié)點只有一個,無論用戶打開多少個文件,都只是對應(yīng)著一個inode結(jié)構(gòu);
//filp就不同,只要打開一個文件,就對應(yīng)著一個file結(jié)構(gòu)體,file結(jié)構(gòu)體通常用來追蹤文件在運行時的狀態(tài)信息
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);
};
2.2 linux字符設(shè)備驅(qū)動的組成
- 字符設(shè)備驅(qū)動模塊加載與卸載函數(shù)
加載函數(shù):設(shè)備號的申請和cdev的注冊
卸載函數(shù):設(shè)備號的釋放和cdev的注銷
image.png
memset是計算機中C/C++語言初始化函數(shù)。作用是將某一塊內(nèi)存中的內(nèi)容全部設(shè)置為指定的值, 這個函數(shù)通常為新申請的內(nèi)存做初始化工作。extern void *memset(void *buffer, int c, int count) buffer:為指針或是數(shù)組, c:是賦給buffer的值, count:是buffer的長度。
cdev_add 和cdev_del分別向系統(tǒng)添加和刪除一個cdev,完成字符設(shè)備的注冊和注銷。
struct xxx_dev_t{
struct cdev cdev;
...
} xxx_dev;
static int _ _init xxx_init(void)
{
cdev_init(&xxx_dev.cdev, &xxx_fops);
xxx_dev.cdev.owner = THIS_MODULE;
ret = cdev_add(&xxx_dev.cdev,xxx_dev_no,1);
static void _ _exit xxx_exit(void)
{
unregister_chrdev_region{xxx_dev_no,1);
//釋放設(shè)備號
cdev_del{&xxx_dev.cdev};
}
- 字符設(shè)備驅(qū)動的file_operations結(jié)構(gòu)體的成員函數(shù)
file_operations中的成員函數(shù)是字符設(shè)備驅(qū)動與內(nèi)核虛擬文件系統(tǒng)的接口,是用戶空間對linux進行系統(tǒng)調(diào)用的最終落實者。以read()為例
ssize_t xxx_read(struct file *filp, char _ _ user *buf, size_t count, loff_t *f_pos)
{
...
copy_to_user(buff,...,...);
}
這里:完成內(nèi)核空間和用戶空間內(nèi)存復(fù)制的是
copy_from_user() 、copy_to_user()
完全復(fù)制成功,返回值為0.

3、編寫一個驅(qū)動
3.1 編寫linux驅(qū)動程序的步驟
- 宏定義與頭文件
- 注冊和注銷設(shè)備文件
- 指定與驅(qū)動相關(guān)的信息
- 指定回調(diào)函數(shù)
- 編寫業(yè)務(wù)邏輯
- 編寫Makefile文件
經(jīng)過上面的6個步驟,可以安裝和卸載linux驅(qū)動
3.2 編寫word_count驅(qū)動
3.2.1 宏定義
#define DEVICE_NAME "wordcount"
#define DEVICE_COUNT 1
#define word_count_MAJOR 0
#define word_count_MINOR 234
#define TRUE -1
#define FALSE 0
static unsigned char mem[10000]
static int major = word_count_MAJOR;
static int minor = word_count_MAJOR;
static dev_t dev_number;
3.2.3 注冊和注銷設(shè)備文件
//
// 定義file_operations結(jié)構(gòu)體
static struct file_operations dev_fops =
{.owner = THIS_MODULE, .read = word_count_read, .write = word_count_write};
// 定義cdev結(jié)構(gòu)體,用于描述字符設(shè)備
static struct cdev word_count_cdev;
//
//
// 創(chuàng)建設(shè)備文件
static int word_count_create_device(void)
{
int ret = 0;
int err = 0;
//初始化cdev的成員,并建立cdev和file_operations之間的連接
cdev_init(&word_count_cdev,&dev_fops);
wordcount_cdev.owner = THIS_MODULE;
if (major >0)
{
// MKDEV:可通過主設(shè)備號和次設(shè)備號生成dev_t
dev_number = MKDEV(major,minor);
// #define DEVICE_NAME "wordcount"
// #define DEVICE_COUNT 1
// 注冊字符設(shè)備區(qū)
err = register_chrdev_region(dev_number,DEVICE_COUNT,DEVICE_NAME);
if (err < 0)
{ printk(kern_warning " register_chrdev_region() failed\n");
retun err;
}
else
{
//隨機分配設(shè)備號,并注冊字符設(shè)備區(qū)
err = alloc_chrdev_region(&word_count_cdev.dev,10,DEVICE_COUNT,DEVICE_NAME);
if (err < 0)
{ printk(kern_warning " register_chrdev_region() failed\n");
retun err;
}
major = MAJOR(word_count_cdev.dev);
minor = MINOR(word_count_cdev.dev);
dev_number = word_count_cdev.dev;
}
//添加字符設(shè)備
ret = cdev_add(&word_count_cdev,dev_number,DEVICE_COUNT);
word_count_class = class_create(TIHIS_MODULE,DEVICE_NAME);
//創(chuàng)建設(shè)備文件
device_create(word_count_class,NULLndev_number,DEVICE_NAME);
return ret;
}
// 銷毀字符設(shè)備
static void word_count_destory_decice(void)
{
// 銷毀字符設(shè)備
device_destroy(word_count_class,dev_number);
// 銷毀class 結(jié)構(gòu)體
if (word_count_class)
class_destroy(word_count_class);
// 注銷字符設(shè)備區(qū)
unregister_chrdev_region(dev_number, DEVICE_COUNT);
return;
}
// 初始化linux wordcount驅(qū)動
static int word_count_init(void)
{
int ret;
ret = word_count_create_device();
return ret;
}
// 卸載wordcount 驅(qū)動
static void word_count_exit(void)
{
word_count_destroy_device();
}
//指定word_count驅(qū)動的初始化和卸載
module_init(word_count_init);
module_exit(word_count_exit);
3.2.4 指定與驅(qū)動相關(guān)的信息
MODULE_AUTHOR("GUQUAN");
MODULE_DESCRIPTION("statistics of word count. ");
MODULE_ALIAS("word count module. ");
MODULE_LICENSE("GPL ");
3.2.5 編寫業(yè)務(wù)邏輯
static char is_spacewhite(char c)
{
if(c == ' ' || c == 9 || c == 13 || c == 10)
return TRUE;
else
return FALSE;
}
//空格、回車、換行分割的字符串作為一個單詞
static int get_word_count(const char *buf)
{
int n = 1;
int i = 0;
char c = ' ';
char flag = 0;// 處理多個空格情況:0:正常情況,1:已遇到一個空格
if(*buf == '\0')
return 0;
//第一個字符是空格,從0開始計數(shù)
if(is_spacewhite(*buf) == TRUE)
n--;
//掃描字符串中的每一個字符,假設(shè):hello world\0
////空格、回車、換行分割的字符串作為一個單詞
for(;(c = *(buf + i)) != '\0'; i++)
{
if(flag == 1 && is_spacewhite(c) == FALSE)
flag = 0;
//多個空格時,忽略
else if(flag == 1 && is_spacewhite(c) == TRUE)
continue;
if(is_spacewhite(c) == TRUE)
{
n++;
flag = 1;
}
}
//如果字符串以一個或多個空格結(jié)尾,不計數(shù)
if(is_spacewhite(*(buf + i - 1)) == TRUE)
n--;
return n;
}
//從設(shè)備文件讀取數(shù)據(jù)
static ssize_t word_count_read(struct file *file, char *buf, size_t count, loff_t *ppos)
{
unsigned char temp[4];
// 將單詞數(shù)(int類型)分解成4個字節(jié)存儲在buf中
temp[0] =word_count >> 24;
temp[1] =word_count >> 16;
temp[2] =word_count >> 8;
temp[3] =word_count;
copy_to_user(buf,(void*) temp,4);
printk(" read:word count:%d",(int) count);
return count;
}
//向設(shè)備文件寫入數(shù)據(jù)
static ssize_t word_count_write(struct file *file, const char *buf, size_t count, loff_t *ppos)
{
ssize_t written = count;
copy_from_user(mem,buf,count);
mem[count] = '\0';
word_count = get_word_count(mem);
printk("writen:word count :%d",(int)word_count);
return written;
}
3.2.6 編寫Makefile文件
obj-m += word_count.o
3.2.7 安裝和卸載word_count驅(qū)動
安裝:insmod word_count.ko
卸載:rmmod word_count
3.2.8 test_main
7 int main(int argc,char *argv[])
8 {
9 int testdev;
10 unsigned char buf[4];
11 testdev = open("/dev/wordcount",O_RDWR);
12
13 if(testdev == -1)
14 {
15 printf("cann't open file,because cann't open /dev \n");
16 return 0;
17 }
18 if(argc >1)
19 {
20 write(testdev,argv[1],strlen(argv[1]));
21 printf("string:%s\n", argv[1]);
22 }
23
24 read(testdev,buf,4);
25
26 int n = 0;
27 n = ((int) buf[0]) << 24 | ((int) buf[1]) << 16 | ((int) buf[2]) << 8 | ((int) buf[3]);
28 printf("word byte display :%d,%d,%d,%d\n", buf[0],buf[1],buf[2],buf[3]);
29
30 printf("word count : %d\n",n);
31 close(testdev);
32 return 0;
33 }
4、遇到的問題
- 掛載:insmod
提示:operation not permitted
解決使用sudo insmod word_count.ko - ./test_word_count時,提示cann't open file,然后使用命令查看權(quán)限:ll /dev
可以看到如下信息:
crw --- --- 1 root root 10, 58 8月 13 16:33 wordcount
image.png
文件類型
linux一共由7種文件類型,分別如下:
-:普通文件
d:目錄文件
l:軟鏈接(類型Windows的快捷方式)
b:塊設(shè)備文件(硬盤、光驅(qū))
p:管道文件
c:字符設(shè)備文件
s:套接口文件/數(shù)據(jù)接口文件(例如啟動Mysql服務(wù)器時產(chǎn)生的mysql.sock文件)
所有者(u表示)權(quán)限,組群(group),其他人(other)
文件權(quán)限:
| r(read) | 4 | 可讀 |
| w(write) | 2 | 可寫 |
| x(execute) | 1 | 可執(zhí)行 |
sudo chmod 777 /dev/wordcount,然后查看,crwxrwxrwx
然后執(zhí)行 ./test_word_count “hello world",可以得到如下結(jié)果


