在此涉及到兩個重要的結構體cdev和file_operations,前者為描述字元裝置,後者為裝置驅動程式的進入點。
對與file_operations的成員描述請看 file_operations中各項解析
我用圖表示下在核心2.6版本後新版本的字元裝置註冊
這是驅動程式應該做的,但是要真正地使用它,我們還必須還要建立裝置節點(linux的裝置操作都是標準的檔案操作,就是當作檔案來處理,所以必鬚生成相應的裝置檔案,在建立時,制定了主裝置號和次裝置號,就和裝置聯絡了起來)和插入核心,不使用的時候從核心中卸載掉
看這個圖
整個的過程貌似這個,虛線框中的是字元驅動應該做的事。驅動與系統核心緊密聯絡的,所以這個程式都有規定好的格式,這也是必須的。
這兩個函數是系統自動調用的
int init_module(void) 和void cleanup_module(void)
看函數名也知道這兩個函數是在什麼時候被調用,這兩個相當於入口和出口,而對硬體的操作函數都在file_operations中被定義。結構體中除了一個資料成員外,其他的都是函數指標。在init的過程中我們需要填充這些指標,當應用程式使用open等系統調用對裝置檔案進行操作的時候,就會通過file_operations將此系統調用對應到驅動程式中的相應的函數來進行具體的操作。
1.裝置號的分配
裝置號是一個數字,用來標示裝置。裝置檔案的建立要指明主裝置號和次裝置號。主裝置號表明裝置的類型,與一個確定的驅動程式對應,次裝置號通常用來標明不同屬性,例如不同的使用方法,不同的位置等,它標誌某個具體的物理裝置。
在2.6中,用dev_t類型來描述裝置號,它是一個32位類型,高12為主裝置號,後20為次裝置號。使用MAJOR和MINOR取主次裝置號,使用MKDEV來合并得到裝置號。
裝置號的分配分為兩種:靜態和動態。
靜態分配知道主裝置號,通過參數指定第一個裝置號,通常次裝置號為0,所以可以這麼得到MKDEV(主裝置號, 0)。
函數為int register_chrdev_region(dev_t first, unsigned int count, char* name)
count為裝置號數目,name為裝置名稱
動態分配通過參數僅需要第一個次裝置號,通常為0,和分配的數目
int alloc_chrdev_region(dev_t *dev, unsigned int fristminor, char* name)
動態時候需要注意,需要儲存分配到的主裝置號,不卸載裝置的時候有麻煩。
釋放已經分配的裝置號使用unregister_chrdev_region(dev_t first, unsigned int count),不管動態還是靜態。
2.字元裝置的註冊
在關聯cdev和裝置號之前,我們想來填充file_operations結構體,不然關聯了也沒有效果。
在linux中檔案的基本操作是開啟,釋放和讀寫,在file_operations中肯定是有的。我們需要需要根據規定的定義(參數格式)來實現他們,我們實現的為
static int demo_open(struct inode *inodeP, struct file *fileP)static int demo_release(struct inode *inodeP, struct file *fileP)static ssize_t demo_read(struct file *fileP, char *buf, size_t count, loff_t *ppos)static ssize_t demo_write(struct file *fileP, const char *buf, size_t count, loff_t *ppos)
填充結構體
static struct file_operations demo_fops = {owner:THIS_MODULE,read:demo_read,write:demo_write,open:demo_open,release:demo_release,};
對cdev結構體的操作有下面幾個函數
void cdev_init(struct cdev *, const struct file_operations *);//初始化,建立cdev和file_operation 之間的串連struct cdev *cdev_alloc(void); //動態申請一個cdev記憶體void cdev_put(struct cdev *p); //釋放int cdev_add(struct cdev *, dev_t, unsigned); //註冊裝置,通常發生在驅動模組的載入函數中void cdev_del(struct cdev *);//登出裝置,通常發生在驅動模組的卸載函數中
下面我貼出整個demo的源碼
#include <linux/module.h>#include <linux/init.h>#include <linux/fs.h>#include <linux/kernel.h>#include <linux/slab.h>#include <linux/types.h>#include <linux/errno.h>#include <linux/cdev.h>#include <linux/uaccess.h>#define DEV_NAME "demo"#define MAX_BUF_SIZE 1024static struct cdev demo_cdev;unsigned int major = 0;static char *data = NULL;/** * [demo_open description] * @param inodeP * @param fileP * @return */static int demo_open(struct inode *inodeP, struct file *fileP){printk("device open success!\n"); data = (char*)kmalloc(sizeof(char) * MAX_BUF_SIZE, GFP_KERNEL); if (!data) { return -ENOMEM; } memset(data, 0, MAX_BUF_SIZE);return 0;}static int demo_release(struct inode *inodeP, struct file *fileP){ printk("release device\n"); if (data) { kfree(data); data = NULL; }return 0;}static ssize_t demo_read(struct file *fileP, char *buf, size_t count, loff_t *ppos){if (count < 0 ) {/* code */return -EINVAL;}if (count > MAX_BUF_SIZE) {count = MAX_BUF_SIZE;}if (copy_to_user(buf, data, count) == EFAULT) {/* code */return -EFAULT;} printk("user read data from device!\n");return count;}static ssize_t demo_write(struct file *fileP, const char *buf, size_t count, loff_t *ppos){if (count < 0 ) {/* code */return -EINVAL;}if (count > MAX_BUF_SIZE) {count = MAX_BUF_SIZE;} memset(data, 0, MAX_BUF_SIZE);if (copy_from_user(data, buf, count) == EFAULT) {return -EFAULT;}printk("user write data to device\n");return count;}static void setup_cdev(struct cdev *dev, int minor, struct file_operations *fops){int err;int devno;devno = MKDEV(major, minor);cdev_init(dev, fops);dev->owner = THIS_MODULE;dev->ops = fops;err = cdev_add(dev, devno, 1);if (err) {printk(KERN_NOTICE" Error %d adding dev %d", err, minor);}}static struct file_operations demo_fops = {owner:THIS_MODULE,read:demo_read,write:demo_write,open:demo_open,release:demo_release,};/** * [init_module description] 載入驅動模組時調用 * @return [description] */int init_module(void) {int re;dev_t dev = MKDEV(major, 0);if (major) {re = register_chrdev_region(dev, 1, DEV_NAME);} else {re = alloc_chrdev_region(&dev, 0, 1, DEV_NAME);}if (re < 0) {printk(KERN_WARNING" Demo dev-->unable to get major %d\n", major);return re;}/** * 如果是動態分配的則要改變原來的major不然在調用setup_cdev時會不能關聯裝置和裝置號, * * 在cleanup_module時也不能從/proc/devices中刪除相應的裝置號項目 */major = MAJOR(dev);setup_cdev(&demo_cdev, 0, &demo_fops);printk("The major of the demo device is %d\n", major);return 0;}void cleanup_module(void){cdev_del(&demo_cdev);unregister_chrdev_region(MKDEV(major, 0), 1);printk("Demo device uninstalled\n");}
Makefile的編寫
在編譯這些核心模組時,建議使用你kbuild
詳細情況見
http://www.mjmwired.net/kernel/Documentation/kbuild/modules.txt
obj-m := demo.oall:$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modulesclean:$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
上面是Makefile檔案
寫了載入和卸載的shell指令碼
#!/bin/bashmodule="demo"device="demo"rm -f /dev/${device}/sbin/insmod -f ./$module.ko $* || exit 1#尋找/proc/devices檔案中demo對應的裝置號major=`cat /proc/devices | awk -v var=${module} '{if($2==var)print $1}'`echo $majormknod /dev/${device} c $major 0
#!/bin/bashmodule="demo"device="demo"/sbin/rmmod $module $* || exit 1rm -f /dev/${device}exit 0
測試的代碼
#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/stat.h>#include <sys/types.h>#include <unistd.h>#include <fcntl.h>#define DEV_NAME "/dev/demo"#define BUF_SIZE 1024int main(int argc, char const *argv[]){int fd;char buffer[BUF_SIZE];fd = open(DEV_NAME, O_RDWR);if (fd < 0) {perror("open dev fail!\n");return -1;} do { printf("Input some worlds to kernel(enter 'quit' to exit)\n"); memset(buffer, 0 ,BUF_SIZE); if (fgets(buffer, BUF_SIZE, stdin) == NULL) { perror("fgets error!\n"); break; } buffer[strlen(buffer) - 1] = '\0'; if (write(fd, buffer, strlen(buffer)) < 0) { perror("write error\n"); break; } if (read(fd, buffer, BUF_SIZE) < 0) { perror("read error\n"); break; } else { printf("The read string is from kernel : %s\n", buffer); } } while(strncmp(buffer, "quit" , 4)); close(fd);return 0;}
無圖無真相
載入和卸載可以用lsmod命令察看,執行了load.sh後,也可以去看看/proc/devives檔案
補充一下,當major,也就是主裝置號為0時,說明是動態註冊。