前期知識html
如何編寫一個簡單的Linux驅動(一)——驅動的基本框架node
前言
linux
在上一篇文章中,咱們學習了驅動的基本框架。這一章,咱們會在上一章代碼的基礎上,繼續對驅動的框架進行完善。要下載上一篇文章的所有代碼,請點擊這裏。git
1.字符設備的四個基本操做程序員
驅動讓用戶程序具有操做硬件設備的能力,那麼對硬件設備有哪些操做呢?在學習編程語言時,咱們都學過對文件的操做,包括打開文件、關閉文件、讀文件、寫文件這四個基本操做。對於Linux來講,一切設備皆文件,因此對設備的基本操做也能夠分爲打開、關閉、讀、寫這四個。而對於設備(已字符設備爲例),Linux提供了一個操做集合——file_operarions。file_operations是一個結構體,其原型以下。github
1 struct file_operations { 2 struct module *owner; 3 loff_t (*llseek) (struct file *, loff_t, int); 4 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 5 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 6 ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); 7 ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); 8 int (*iterate) (struct file *, struct dir_context *); 9 int (*iterate_shared) (struct file *, struct dir_context *); 10 unsigned int (*poll) (struct file *, struct poll_table_struct *); 11 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 12 long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 13 int (*mmap) (struct file *, struct vm_area_struct *); 14 int (*open) (struct inode *, struct file *); 15 int (*flush) (struct file *, fl_owner_t id); 16 int (*release) (struct inode *, struct file *); 17 int (*fsync) (struct file *, loff_t, loff_t, int datasync); 18 int (*fasync) (int, struct file *, int); 19 int (*lock) (struct file *, int, struct file_lock *); 20 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); 21 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 22 int (*check_flags)(int); 23 int (*flock) (struct file *, int, struct file_lock *); 24 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); 25 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 26 int (*setlease)(struct file *, long, struct file_lock **, void **); 27 long (*fallocate)(struct file *file, int mode, loff_t offset, 28 loff_t len); 29 void (*show_fdinfo)(struct seq_file *m, struct file *f); 30 #ifndef CONFIG_MMU 31 unsigned (*mmap_capabilities)(struct file *); 32 #endif 33 ssize_t (*copy_file_range)(struct file *, loff_t, struct file *, 34 loff_t, size_t, unsigned int); 35 int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t, 36 u64); 37 ssize_t (*dedupe_file_range)(struct file *, u64, u64, struct file *, 38 u64); 39 }
要使用該結構體,須要包含頭文件"linux/fs.h"。該結構體中的成員變量不少,但在本章中,咱們只用到打開(open)、關閉(release)、讀(read)、寫(write)這四個成員變量,以及一個默認須要的全部者(owner)成員變量。 編程
1 struct file_operations { 2 ... 3 struct module *owner; 4 int (*open) (struct inode *, struct file *); 5 int (*release) (struct inode *, struct file *); 6 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 7 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 8 ... 9 }
file_operations結構體的成員變量嚮應用程序提供一個對設備操做的接口,可是接口的具體操做須要咱們本身來實現。打開上一章所寫的驅動源代碼"shanwuyan.c",定義一個"file_operations"類型的結構體,再定義四個函數"shanwuyan_open"、"shanwuyan_release"、"shanwuyan_read"、"shanwuyan_write",讓file_operations結構體變量的成員變量初始化爲這四個函數。 app
1 /*打開設備*/ 2 static int shanwuyan_open(struct inode *inode, struct file *filp) 3 { 4 return 0; 5 } 6 7 /*釋放(關閉)設備*/ 8 static int shanwuyan_release(struct inode *inode, struct file *filp) 9 { 10 return 0; 11 } 12 13 /*讀設備*/ 14 static ssize_t shanwuyan_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) 15 { 16 return 0; 17 } 18 19 /*寫設備*/ 20 static ssize_t shanwuyan_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) 21 { 22 return 0; 23 } 24 25 static struct file_operations shanwuyan_fops = 26 { 27 .owner = THIS_MODULE, //默認 28 .open = shanwuyan_open, //打開設備 29 .release = shanwuyan_release, //關閉設備 30 .read = shanwuyan_read, //讀設備 31 .write = shanwuyan_write, //寫設備 32 };
這樣,用戶在使用庫函數"open"打開設備時,就會調用函數"shanwuyan_open";用"close"函數關閉設備時,就會調用函數"shanwuyan_release";用"read"函數讀設備時,就會調用函數"shanwuyan_read";用"write"函數寫設備時,就會調用函數"shanwuyan_write"。爲了讓這四個函數的調用更直觀地爲程序員所觀察,咱們能夠在這四個函數中添加打印語句,這樣每次對設備進行操做的時候,程序員都能在終端觀察到相應的信息,以下方代碼。 框架
1 /*打開設備*/ 2 static int shanwuyan_open(struct inode *inode, struct file *filp) 3 { 4 printk(KERN_EMERG "shanwuyan_open\r\n"); 5 return 0; 6 } 7 8 /*釋放(關閉)設備*/ 9 static int shanwuyan_release(struct inode *inode, struct file *filp) 10 { 11 printk(KERN_EMERG "shanwuyan_close\r\n"); 12 return 0; 13 } 14 15 /*讀設備*/ 16 static ssize_t shanwuyan_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) 17 { 18 printk(KERN_EMERG "shanwuyan_read\r\n"); 19 return 0; 20 } 21 22 /*寫設備*/ 23 static ssize_t shanwuyan_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) 24 { 25 printk(KERN_EMERG "shanwuyan_write\r\n"); 26 return 0; 27 }
2.註冊與註銷字符設備async
字符設備的註冊是在入口函數"shanwuyan_init"中完成的,字符設備的註銷是在出口函數"shanwuyan_exit"中完成的。在上一篇文章中,這兩個函數的做用只是打印一行字符串,並無註冊和註銷字符設備的功能。在本章,咱們將完善這兩個函數。
首先介紹一個函數"register_chrdev",函數原型以下。
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops); //major是主設備號,name是設備名,fops是字符設備操做集的地址
該函數的做用是註冊字符設備,設備號爲程序員給定的一個主設備號major,設備名爲用戶給定的一個字符串,字符操做集爲上文中定義的結構體地址。若是函數該函數返回值爲負數,說明設備註冊失敗,不然說明設備註冊成功。
接下來介紹註銷字符設備的函數"unregister_chrdev",該函數的原型以下。
static inline void unregister_chrdev(unsigned int major, const char *name); //major是主設備號,name是設備名
該函數的做用是註銷字符設備。
打開開發板的系統終端,輸入命令"cat /proc/devices"能夠查看有哪些設備號已經被佔用。通過查看,本系統的設備號"200"處於空閒狀態,能夠用來註冊字符設備。
完善入口函數和出口函數,代碼以下。
1 ... 2 #define SHANWUYAN_MAJOR 200 //程序員給定的主設備號 3 #define SHANWUYAN_NAME "shanwuyan" //程序員給定的設備名字符串 4 ... 5 static struct file_operations shanwuyan_fops = 6 { 7 ... 8 } //定義的字符設備操做集 9 static int __init shanwuyan_init(void) //驅動入口函數 10 { 11 int ret = 0; 12 13 ret = register_chrdev(SHANWUYAN_MAJOR, SHANWUYAN_NAME, &shanwuyan_fops); 14 if(ret < 0) 15 printk(KERN_EMERG "init failed\r\n"); //註冊失敗 16 else 17 printk(KERN_EMERG "shanwuyan_init\r\n");//註冊成功 18 return 0; 19 } 20 static void __exit shanwuyan_exit(void) //驅動出口函數 21 { 22 unregister_chrdev(SHANWUYAN_MAJOR, SHANWUYAN_NAME); //註銷字符設備 23 printk(KERN_EMERG "shanwuyan_exit\r\n"); 24 } 25 ...
這樣,一個字符設備驅動的雛形就完成了。
3.編寫應用程序
編寫一個應用程序,包含對設備的打開、關閉、讀和寫的操做。源代碼以下
1 //文件名爲"shanwuyan_APP.c" 2 #include <sys/types.h> 3 #include <sys/stat.h> 4 #include <fcntl.h> 5 #include <stdio.h> 6 #include <unistd.h> 7 #include <stdlib.h> 8 #include <string.h> 9 10 /* 11 *argc:應用程序參數個數,包括應用程序自己 12 *argv[]:具體的參數內容,字符串形式 13 *./shanwuyan_APP <filename> <r:w> r表示讀,w表示寫 14 */ 15 int main(int argc, char *argv[]) 16 { 17 int ret = 0; 18 int fd = 0; 19 char *filename; 20 21 if(argc != 3) //共有三個參數 22 { 23 printf("Error usage!\r\n"); 24 return -1; 25 } 26 27 filename = argv[1]; //獲取文件名稱 28 29 fd = open(filename, O_RDWR); 30 if(fd < 0) 31 { 32 printf("cannot open file %s\r\n", filename); 33 return -1; 34 } 35 36 if(!strcmp(argv[2], "r")) //讀設備 37 { 38 39 read(fd, NULL, 0); //只是使用讀函數,但不讀出數據 40 } 41 else if(!strcmp(argv[2], "w")) //寫設備 42 { 43 write(fd, NULL, 0); //只是使用寫函數,但並不向設備寫數據 44 45 } 46 else 47 { 48 printf("ERROR usage!\r\n"); 49 } 50 51 /*關閉設備*/ 52 close(fd); 53 54 return 0; 55 }
4.應用
編譯驅動文件,交叉編譯應用程序,拷貝到開發板中,並加載驅動。
驅動加載完成後,使用命令"mknod /dev/shanwuyan c 200 0",在"/dev"目錄下建立"shanwuyan"設備節點。其中參數"c"是指建立一個字符設備節點,200表示主設備號,0表示次設備號。而後使用ls命令查看是否建立成功。
分別輸入命令"./shanwuyan_APP /dev/shanwuyan r"和命令"./shanwuyan_APP /dev/shanwuyan w",能夠看到終端打印了以下信息。能夠看到,應用程序打開設備、關閉設備、讀設備、寫設備的操做都有所體現。
在本章中,咱們只是單純得調用了read和write函數,可是並無真正的讀寫數據。讀寫數據操做將在下一章中出現。
本章的所有代碼在這裏。