如何編寫一個簡單的Linux驅動(二)——裝置操作集file_operations

山無言發表於2020-09-12

前期知識

  如何編寫一個簡單的Linux驅動(一)——驅動的基本框架

前言

  在上一篇文章中,我們學習了驅動的基本框架。這一章,我們會在上一章程式碼的基礎上,繼續對驅動的框架進行完善。要下載上一篇文章的全部程式碼,請點選這裡

1.字元裝置的四個基本操作

  驅動讓使用者程式具備操作硬體裝置的能力,那麼對硬體裝置有哪些操作呢?在學習程式語言時,我們都學過對檔案的操作,包括開啟檔案、關閉檔案、讀檔案、寫檔案這四個基本操作。對於Linux來說,一切裝置皆檔案,所以對裝置的基本操作也可以分為開啟、關閉、讀、寫這四個。而對於裝置(已字元裝置為例),Linux提供了一個操作集合——file_operarions。file_operations是一個結構體,其原型如下。

 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結構體變數的成員變數初始化為這四個函式。  

 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.註冊與登出字元裝置

  字元裝置的註冊是在入口函式"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函式,但是並沒有真正的讀寫資料。讀寫資料操作將在下一章中出現。

  本章的全部程式碼在這裡

  

相關文章