struct file_operations
結構體
- 實例化本身的字符設備結構體類(實例化對象),使用
kmalloc
- 初始化通用字符設備(
cdev_init
)/* 功能:初始化字符設備 參數: @cdev:cdev結構體 @fops:操做字符設備的函數接口地址 返回值:無 */ void cdev_init(struct cdev *cdev, const struct file_operation *fops)
- 申請設備號(
register_chrdev_region
)/* 功能:註冊設備號 參數: @from:設備號 @count:註冊的設備個數 @name:設備名字 返回值:註冊成功返回0,失敗返回錯誤碼(負數) */ int register_chrdev_region(dev_t from, unsigned count, const char *name)
- 添加字符設備到操做系統(
cdev_add
)/* 功能:添加一個字符設備驅動到操做系統 參數: @p:cdev結構體地址 @dev:設備號 @count:次設備號個數 返回值:註冊成功返回0,失敗返回錯誤碼(負數) */ int cdev_add(struct cdev *p, dev_t dev, unsigned count)
- 釋放給字符設備類分配的空間(
kfree
)- 移除註冊的字符設備(
cdev_del
)/* 功能:從系統中刪除一個字符設備驅動 參數: @p:cdev結構體地址 返回值:無 */ void cdev_del(struct cdev *p)
- 釋放申請的設備號(
unregister_chrdev_region
)/* 功能:釋放申請的設備號 參數: @from:設備號 @count:次設備號個數 返回值:無 */ void unregister_chrdev_region(dev_t from, unsigned count)
obj-m := cdev_test.o KDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) modules: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) clean
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { dev_t dev_no; //device major number char buffer[BUF_SIZR]; //my private memory struct cdev cdev; //char device class }; /* step1: malloc memory for char device */ static struct demo_cdev demo_dev; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { printk(KERN_INFO "Enter: %s\n", __func__); return 0; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); return 0; } static int demo_release(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); return 0; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, }; static int __init demo_init(void) { int ret = -1; printk(KERN_INFO "Enter: %s\n", __func__); /* step2: init for demo char device*/ cdev_init(&demo_dev.cdev, &demo_operation); /* step3: apply a major device number for char device */ demo_dev.dev_no = MKDEV(MAJOR_NUM, MINOR_NUM); /* step4: register char device number */ ret = register_chrdev_region(demo_dev.dev_no, 1, "demo_chrdev"); if (ret < 0) { printk(KERN_ERR "faild to register device number\n"); return ret; } /* step4: add char device to OS */ ret = cdev_add(&demo_dev.cdev, demo_dev.dev_no, 1); if (ret < 0) { printk(KERN_ERR "cdev add failed\n"); /* failed to add cdev, we should unregister cdev before return*/ unregister_chrdev_region(demo_dev.dev_no, 1); return ret; } printk(KERN_INFO "demo char device init done\n"); return 0; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); cdev_del(&demo_dev.cdev); unregister_chrdev_region(demo_dev.dev_no, 1); printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo mknod /dev/cdev_test c 168 0
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls -l /dev/cdev_test crw-r--r-- 1 root root 168, 0 3月 7 15:53 /dev/cdev_test
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod cdev_test.ko
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg [24936.071335] Enter: demo_init [24936.071337] demo char device init done
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_test hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg [24936.071335] Enter: demo_init [24936.071337] demo char device init done [25542.812769] Enter: demo_open [25542.812777] Enter: demo_read [25542.812784] Enter: demo_release
- 不足1:上述驅動中,在註冊設備號的時候,咱們是直接指定一個特定的主設備號,若是系統中已經存在這個設備號,那麼將會致使驅動註冊失敗
- 改進1:當指定的設備號註冊失敗的時候,咱們能夠採用動態註冊,讓linux系統給咱們分配一個未使用過的設備號
/* 功能:讓系統自動分配一個設備號,以後再註冊設備 參數: @dev:用來獲取系統分配的設備號 @baseminor:第一個次設備號 @count:次設備號的個數 @name:設備名稱 返回值:成功返回0,失敗返回錯誤碼 */ int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name) /* 功能:建立一個類 參數: @owner:通常寫爲THIS_MODULE @name:字符串名字,本身寫 返回值:class結構體類型指針,可經過IS_ERR(cls)判斷是否建立失敗,成功返回0,失敗返回非0(可經過PTR_ERR(cls)來獲取失敗的返回碼) */ struct class *class_create(owner, name); /* 功能:建立一個設備文件 參數: @class:class_create建立的class結構體類型指針 @parent:父親,通常直接寫NULL @devt:設備號,經過MDKEV()宏建立 @drvdata:私有數據,通常寫NULL @fmt:可變參數,通常爲字符串,表示設備節點名字 返回值:device類型指針 */ struct device *device_create(struct class * class, struct device * parent, dev_t devt, void * drvdata, const char * fmt, ...);
不足2:每次裝載完驅動以後都須要手動去建立設備節點,麻煩node
改進2:當咱們註冊完設備以後,讓linux系統自動在/dev目錄下建立設備節點linux
不足3:字符設備讀寫函數接口沒有實際數據流向程序員
改進3:使用xxx_read函數將設備裏面的內容拷貝到用戶空間;使用xxx_write函數將用戶空間的數據寫入設備內部buffershell
/* 功能:從用戶空間拷貝數據到內核空間(大量數據) 參數: @to:內核空間地址 @from:用戶空間地址 @n:數據大小 返回值:成功返回0,失敗返回未被拷貝的字節數 注意:此處__user是一個空的宏,主要用來顯示的告訴程序員它修飾的指針變量存放的是用戶空間的地址,這是一個好習慣 */ int copy_from_user(void *to, const void __user *from, int n) /* 功能:從內核空間拷貝數據到用戶空間(大量數據) 參數: @to:用戶空間地址 @from:內核空間地址 @n:數據大小 返回值:成功返回0,失敗返回未被拷貝的字節數 */ int copy_to_user(void __user *to, const void *from, int n) /* 宏功能:檢查用戶空間傳遞到內核空間的地址是否合法 參數: @type:VERIFY_READ:用戶空間的地址是否可讀 VERIFY_WRITE:用戶空間的地址是否可寫 @addr:用戶空間傳遞的地址 @szie:檢查的空間大小 返回值:成功返回非0,失敗返回0 */ access_ok(type, addr, size) /* 宏功能:拷貝單個數據到用戶空間 參數: @x:須要拷貝到用戶空間的地址 @ptr:用戶空間的地址 返回值:成功返回0,失敗返回-EFAULT 注意:此宏使用前須要使用access_ok宏檢查ptr是否有效 */ put_user(x, ptr) / __put_user(x, ptr) /* 宏功能:拷貝單個數據到內核空間 參數: @x:須要拷貝到用戶空間的地址 @ptr:用戶空間的地址 返回值:成功返回0,失敗返回-EFAULT 注意:此宏使用前須要使用access_ok宏檢查ptr是否有效 */ get_user(x, ptr) / __get_user(x, ptr)
- 不足4:設備的空間是在data段分配的,假如設備內部buff過大,將佔用data段很大一部份內存
- 改進4:分配堆內存給設備
/* 功能:分配內存 參數: @size:內存大小 @flags:經常使用GFP_KERNEL 返回值:成功返回分配的內存首地址,失敗返回NULL */ static __always_inline void *kmalloc(size_t size, gfp_t flags)
/* 功能:對應用戶空間的read,將內核空間數據發送給用戶空間 參數: @filp:file結構體指針,裏面包含文件描述符fd @buff:用戶空間地址,用來保存內核發送過去的數據 @count:用戶空間要讀的數據長度 @fops:用戶空間讀取數據的當前偏移量 注意:關於fops的一些解釋 假設用戶空間調用read(fd, buf, 10)函數,此時內核空間一共給用戶空間發送1024個字節,可是用戶空間此刻只讀 取了10個字節,故此刻偏移量爲10,那麼下次用戶再次讀取10字節的時候就須要從11的偏移量開始。 1.因此在編寫內核的read函數時候,咱們須要先判斷當前偏移量是否已經超過了內核發送給用戶數據的最大值,此處不 能大於等於1024。 2.由於用戶是按10字節爲單位進行讀取,那麼讀到最後確定不是10的倍數,假設此時內核還剩餘4字節數據沒被用戶讀 取,那麼此刻用戶再讀10字節的時候內核就應該只返回4字節數據 */ ssize_t xxx_read (struct file *filp, char __user *buff, size_t count, loff_t *fpos) /* 功能: */ ssize_t xxx_write (struct file *filp, const char __user *buff, size_t count, loff_t *fops)
struct file
結構體描述,該結構體描述以下,其中有一個變量void *private_data
,這個私有數據能夠用來保存咱們的私有變量,因此咱們能夠將咱們建立的驅動保存到這個變量下面,當使用xxx_read
,xxx_write
等函數的時候就能夠經過private來訪問咱們驅動內部的數據struct file { ... /* needed for tty driver, and maybe others */ void *private_data; ... } /*用法示例*/ struct demo_cdev { dev_t dev_no; //device major number char *buffer; //my private memory struct cdev cdev; //char device class struct class *cls; struct device *device; }; struct demo_cdev *demo_dev; static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); file->private_data = (void *)demo_dev; return 0; } static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; }
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <linux/slab.h> #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { dev_t dev_no; //device major number char *buffer; //my private memory struct cdev cdev; //char device class struct class *cls; struct device *device; }; /* step1: malloc memory for char device */ struct demo_cdev *demo_dev; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); /* determine whether user has finished reading data */ if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { return -EFAULT; } *pos += read_bytes; return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { return -EFAULT; } *pos += write_bytes; return write_bytes; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); file->private_data = (void *)demo_dev; return 0; } static int demo_release(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); return 0; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, }; static int __init demo_init(void) { int ret = -1; demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL); if (!demo_dev) { printk(KERN_ERR "failed to malloc demo_dev\n"); ret = -ENOMEM; goto ERROR_MALLOC_DEMODEV; } demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL); if (!demo_dev->buffer) { printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR); ret = -ENOMEM; goto ERROR_MALLOC_BUFFER; } memset(demo_dev->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "Enter: %s\n", __func__); /* step2: init for demo char device*/ cdev_init(&demo_dev->cdev, &demo_operation); /* step3: apply a major device number for char device */ demo_dev->dev_no = MKDEV(MAJOR_NUM, MINOR_NUM); /* step4: register char device number */ ret = register_chrdev_region(demo_dev->dev_no, 1, "demo_chrdev"); if (ret < 0) { /* if we faild to register c_dev number,we can let OS to register cdev number */ ret = alloc_chrdev_region(&demo_dev->dev_no, 0, 1, "demo_chrdev"); if (ret < 0) { printk(KERN_ERR "faild to register device number\n"); goto ERROR_CHRDEV_REGION; } } /* step5: add char device to OS */ ret = cdev_add(&demo_dev->cdev, demo_dev->dev_no, 1); if (ret < 0) { printk(KERN_ERR "cdev add failed\n"); /* failed to add cdev, we should unregister cdev before return*/ goto ERROR_CDEV_ADD; } /* create a cdev class in /sys/class/cdev_test/ */ demo_dev->cls = class_create(THIS_MODULE, "cdev_test"); if (IS_ERR(demo_dev->cls)) { ret = PTR_ERR(demo_dev->cls); goto ERR_CLASS_CREATE; } /* create a cdev device in /sys/class/cdev_test/cdev_mem */ demo_dev->device = device_create(demo_dev->cls, NULL, demo_dev->dev_no, NULL, "cdev_mem"); if (IS_ERR(demo_dev->device)) { ret = PTR_ERR(demo_dev->device); goto ERROR_DEVICE_CREATE; } printk(KERN_INFO "demo char device init done\n"); return 0; ERROR_DEVICE_CREATE: class_destroy(demo_dev->cls); ERR_CLASS_CREATE: cdev_del(&demo_dev->cdev); ERROR_CDEV_ADD: unregister_chrdev_region(demo_dev->dev_no, 1); ERROR_CHRDEV_REGION: kfree(demo_dev->buffer); demo_dev->buffer = NULL; ERROR_MALLOC_BUFFER: kfree(demo_dev); demo_dev = NULL; ERROR_MALLOC_DEMODEV: return ret; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); cdev_del(&demo_dev->cdev); unregister_chrdev_region(demo_dev->dev_no, 1); kfree(demo_dev); demo_dev = NULL; kfree(demo_dev->buffer); demo_dev->buffer = NULL; printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls cdev_test_v1.c cdev_test_v3.c cdev_test_v3.mod cdev_test_v3.mod.o Makefile Module.symvers cdev_test_v2.c cdev_test_v3.ko cdev_test_v3.mod.c cdev_test_v3.o modules.order hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod cdev_test_v3.ko [sudo] hq 的密碼: hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls -al /dev/cdev_mem crw------- 1 root root 168, 0 3月 8 15:48 /dev/cdev_mem hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/cdev_mem hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hq" -> /dev/cdev_mem hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_mem hq - hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hanqi" -> /dev/cdev_mem hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_mem hanqi - hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$
ioctl
是設備驅動程序中設備控制接口函數,一個字符設備驅動一般會實現設備打開、關閉、讀、寫等功能,在一些須要細分的情境下,若是須要擴展新的功能,一般以增設 ioctl() 命令的方式實現。api
#include <sys/ioctl.h> /* 功能:實現IO控制 參數: @fd:文件描述符 @cmd:交互協議,設備驅動將根據 cmd 執行對應操做 @...:可變參數 arg,依賴 cmd 指定長度以及類型 返回值:執行成功時返回0,失敗則返回-1並設置全局變量errorno值 */ int ioctl(int fd, int cmd, ...) ;
- 不傳遞參數給xxx_ioctl
- 傳遞參數給xxx_ioctl,並但願能將指令寫入設備(如設置串口波特率)
- 調用xxx_ioctl函數獲取硬件信息(如獲取當前串口波特率)
/* 功能:unlocked_ioctl,在無大內核鎖(BKL)的狀況下調用;compat_ioctl,compat 全稱 compatible(兼容的),主要目的是爲 64 位系統提供 32 位 ioctl 的兼容方法,也是在無大內核鎖的狀況下調用 參數: @file:file結構體,其中保存了文件描述符 @cmd:用戶空間傳遞給內核的指令 @arg:可變參數,可供用戶空間與內核空間之間傳參 */ long (*unlocked_ioctl) (struct file *file, unsigned int cmd, unsigned long arg); long (*compat_ioctl) (struct file *file, unsigned int cmd, unsigned long arg);
| 設備類型 | 序列號 | 方向 |數據尺寸| |----------|--------|-------|-----------| | 8 bit | 8 bit |2 bit |8~14 bit| |----------|--------|-------|-----------|
- 設備類型:表明一類設備,通常用一個字母或者一個8bit的數字表示。在一些文獻中翻譯爲 「幻數」 或者 「魔數」,能夠爲任意 char 型字符,例如‘a’、’b’、’c’ 等等,其主要做用是使 ioctl 命令有惟一的設備標識;
- 序列號:表示這個設備的第幾個命令
- 方向:這個命令用來操做的方式,如:只讀,只寫,讀寫或者其餘; _IOC_NONE(無數據)、_IOC_READ(讀數據)、_IOC_WRITE(寫數據)、_IOC_READ | _IOC_WRITE(讀寫數據)
- 數據尺寸:即用戶傳遞的數據大小
/*生成命令*/ /* @type:設備類型 @nr:命令序號 @size:用戶傳遞的數據類型(int,char,struct name...) */ #define _IO(type,nr) //沒有數據傳遞的命令 #define _IOR(type,nr,size) //從驅動中讀取數據 #define _IOW(type,nr,size) //向驅動中寫入數據 #define _IOWR(type,nr,size) //雙向傳輸數據 ----------------------------------------------------------------------------------------------------- _IOC_NONE //值爲0,表示無數據傳輸 IOC_READ //值爲1,表示從驅動中讀取數據 _IOC_WRITE //值爲2,向驅動中寫入數據 IOC_READ | _IOC_WRITE //雙向數據傳輸 ----------------------------------------------------------------------------------------------------- /*解析命令*/ #define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK) //從命令中提取數據流向 #define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK) //從命令中提取設備類型 #define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK) ///從命令中提取序列號 #define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK) ///從命令中提取數據大小
#define DEV_FIFO_TYPE 'k' #define DEV_FIFO_CLEAN _IO(DEV_FIFO_TYPE, 0x10) #define DEV_FIFO_GETVALUE _IOR(DEV_FIFO_TYPE, 0x11, int) #define DEV_FIFO_SETVALUE _IOW(DEV_FIFO_TYPE, 0x12, int)
cdev_test_v4.h
#ifndef __CDEV_TEST_V4_H__ #define __CDEV_TEST_V4_H__ #include <linux/ioctl.h> #define CDEV_TEST_V4_TYPE 'h' #define CDEV_TEST_V4_CLEAN _IO(CDEV_TEST_V4_TYPE, 0x10) #define CDEV_TEST_V4_SETVAL _IOW(CDEV_TEST_V4_TYPE, 0x11, int) #define CDEV_TEST_V4_GETVAL _IOR(CDEV_TEST_V4_TYPE, 0x12, int) #endif
cdev_test_v4.c
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <linux/slab.h> #include "cdev_test_v4.h" #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { dev_t dev_no; //device major number char *buffer; //my private memory int value; struct cdev cdev; //char device class struct class *cls; struct device *device; }; /* step1: malloc memory for char device */ struct demo_cdev *demo_dev; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); /* determine whether user has finished reading data */ if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { return -EFAULT; } *pos += read_bytes; return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { return -EFAULT; } *pos += write_bytes; return write_bytes; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); file->private_data = (void *)demo_dev; return 0; } static int demo_release(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); return 0; } static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; struct demo_cdev *demo = file->private_data; switch(cmd) { case CDEV_TEST_V4_CLEAN: memset(demo->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "cmd: clean\n"); break; case CDEV_TEST_V4_GETVAL: put_user(demo->value, (int *)arg); printk(KERN_INFO "cmd: getval\n"); break; case CDEV_TEST_V4_SETVAL: demo->value = (int)arg; printk(KERN_INFO "cmd: setval\n"); break; default: break; } return (long)ret; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, .unlocked_ioctl = demo_ioctl, }; static int __init demo_init(void) { int ret = -1; demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL); if (!demo_dev) { printk(KERN_ERR "failed to malloc demo_dev\n"); ret = -ENOMEM; goto ERROR_MALLOC_DEMODEV; } demo_dev->value = 1; demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL); if (!demo_dev->buffer) { printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR); ret = -ENOMEM; goto ERROR_MALLOC_BUFFER; } memset(demo_dev->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "Enter: %s\n", __func__); /* step2: init for demo char device*/ cdev_init(&demo_dev->cdev, &demo_operation); /* step3: apply a major device number for char device */ demo_dev->dev_no = MKDEV(MAJOR_NUM, MINOR_NUM); /* step4: register char device number */ ret = register_chrdev_region(demo_dev->dev_no, 1, "demo_chrdev"); if (ret < 0) { /* if we faild to register c_dev number,we can let OS to register cdev number */ ret = alloc_chrdev_region(&demo_dev->dev_no, 0, 1, "demo_chrdev"); if (ret < 0) { printk(KERN_ERR "faild to register device number\n"); goto ERROR_CHRDEV_REGION; } } /* step5: add char device to OS */ ret = cdev_add(&demo_dev->cdev, demo_dev->dev_no, 1); if (ret < 0) { printk(KERN_ERR "cdev add failed\n"); /* failed to add cdev, we should unregister cdev before return*/ goto ERROR_CDEV_ADD; } /* create a cdev class in /sys/class/cdev_test/ */ demo_dev->cls = class_create(THIS_MODULE, "cdev_test"); if (IS_ERR(demo_dev->cls)) { ret = PTR_ERR(demo_dev->cls); goto ERR_CLASS_CREATE; } /* create a cdev device in /sys/class/cdev_test/cdev_mem */ demo_dev->device = device_create(demo_dev->cls, NULL, demo_dev->dev_no, NULL, "cdev_mem"); if (IS_ERR(demo_dev->device)) { ret = PTR_ERR(demo_dev->device); goto ERROR_DEVICE_CREATE; } printk(KERN_INFO "demo char device init done\n"); return 0; ERROR_DEVICE_CREATE: class_destroy(demo_dev->cls); ERR_CLASS_CREATE: cdev_del(&demo_dev->cdev); ERROR_CDEV_ADD: unregister_chrdev_region(demo_dev->dev_no, 1); ERROR_CHRDEV_REGION: kfree(demo_dev->buffer); demo_dev->buffer = NULL; ERROR_MALLOC_BUFFER: kfree(demo_dev); demo_dev = NULL; ERROR_MALLOC_DEMODEV: return ret; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); device_destroy(demo_dev->cls, MKDEV(MAJOR_NUM, MINOR_NUM)); class_destroy(demo_dev->cls); cdev_del(&demo_dev->cdev); unregister_chrdev_region(demo_dev->dev_no, 1); kferr(demo_dev->buffer); demo_dev->buffer = NULL; kfree(demo_dev); demo_dev = NULL; printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <fcntl.h> #include "cdev_test_v4.h" #define DEVICE_PATH "/dev/cdev_mem" int main(void) { int fd; int ret; int n; int value; char buf[10] = "hanqi"; fd = open(DEVICE_PATH, O_RDWR); if (fd < 0) { printf("failed to open\n"); return -1; } n = write(fd, buf, sizeof(buf)); if (n < 0) { printf("failed to write\n"); close(fd); return -1; } printf("write to cdev %d bytes\n", n); ret = ioctl(fd, CDEV_TEST_V4_GETVAL, &value); if (ret < 0) { printf("failed to ioctl\n"); close(fd); return -1; } printf("value = %d\n", value); ret = ioctl(fd, CDEV_TEST_V4_SETVAL, 520); if (ret < 0) { printf("failed to ioctl\n"); close(fd); return -1; } ret = ioctl(fd, CDEV_TEST_V4_GETVAL, &value); if (ret < 0) { printf("failed to ioctl\n"); close(fd); return -1; } printf("value = %d\n", value); close(fd); return 0; }
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod cdev_test_v4.ko [sudo] hq 的密碼: hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/cdev_mem hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ gcc test_cdev4.h -o test hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test write to cdev 10 bytes value = 1 value = 520 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/cdev_mem hanqi hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg [ 1970.376549] Enter: demo_init [ 1970.376612] demo char device init done [ 3117.086360] Enter: demo_open [ 3117.086368] Enter: demo_write [ 3117.086515] cmd: getval [ 3117.086521] cmd: setval [ 3117.086522] cmd: getval [ 3117.086527] Enter: demo_release [ 3263.306387] Enter: demo_open [ 3263.306403] Enter: demo_read [ 3263.306523] Enter: demo_read [ 3263.306536] Enter: demo_release
上述過程建立一個字符設備驅動流程比較複雜,爲此引入了Misc設備驅動,簡化字符設備驅動的建立流程。其主設備號是固定的爲10。bash
struct miscdevice { int minor; //次設備號,若爲MISC_DYNAMIC_MINOR就自動分配 const char *name; //設備名 const struct file_operations *fops; //設備文件操做結構體 struct list_head list; //misc_list鏈表頭 struct device *parent; struct device *this_device; const char *nodename; mode_t mode; };
/* 在加載模塊的時候會自動建立設備文件,其主設備號爲10 */ int misc_register(struct miscdevice* misc); /* 在卸載模塊的時候自動刪除設備文件 */ int misc_dergister(struct miscdevice* misc);
static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <asm/uaccess.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include "cdev_test_v4.h" #define BUF_SIZR 1024 #define MAJOR_NUM 168 #define MINOR_NUM 0 struct demo_cdev { char *buffer; //my private memory int value; struct miscdevice *mdev; }; /* step1: malloc memory for char device */ struct demo_cdev *demo_dev; static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos) { int ret = -1; int read_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); /* determine whether user has finished reading data */ if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { read_bytes = BUF_SIZR - *pos; } else { read_bytes = size; } ret = copy_to_user(buf, kbuf, read_bytes); if (ret != 0) { return -EFAULT; } *pos += read_bytes; return read_bytes; } static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos) { int ret; int write_bytes; struct demo_cdev *demo = file->private_data; char *kbuf = demo->buffer + *pos; printk(KERN_INFO "Enter: %s\n", __func__); if (*pos >= BUF_SIZR) { return 0; } if (size > (BUF_SIZR - *pos)) { write_bytes = BUF_SIZR - *pos; } else { write_bytes = size; } ret = copy_from_user(kbuf, buf, write_bytes); if (ret != 0) { return -EFAULT; } *pos += write_bytes; return write_bytes; } static int demo_open(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); file->private_data = (void *)demo_dev; return 0; } static int demo_release(struct inode *node, struct file *file) { printk(KERN_INFO "Enter: %s\n", __func__); return 0; } static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { int ret = 0; struct demo_cdev *demo = file->private_data; switch(cmd) { case CDEV_TEST_V4_CLEAN: memset(demo->buffer, 0x00, BUF_SIZR); printk(KERN_INFO "cmd: clean\n"); break; case CDEV_TEST_V4_GETVAL: put_user(demo->value, (int *)arg); printk(KERN_INFO "cmd: getval\n"); break; case CDEV_TEST_V4_SETVAL: demo->value = (int)arg; printk(KERN_INFO "cmd: setval\n"); break; default: break; } return (long)ret; } static struct file_operations demo_operation= { .open = demo_open, .release = demo_release, .read = demo_read, .write = demo_write, .unlocked_ioctl = demo_ioctl, }; static struct miscdevice misc_struct = { .minor = MISC_DYNAMIC_MINOR, .name = "misc_dev", .fops = &demo_operation, }; static int __init demo_init(void) { int ret = -1; printk(KERN_INFO "Enter: %s\n", __func__); demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL); if (!demo_dev) { printk(KERN_ERR "failed to malloc demo_dev\n"); ret = -ENOMEM; goto ERROR_MALLOC_DEMODEV; } demo_dev->value = 1; demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL); if (!demo_dev->buffer) { printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR); ret = -ENOMEM; goto ERROR_MALLOC_BUFFER; } memset(demo_dev->buffer, 0x00, BUF_SIZR); demo_dev->mdev = &misc_struct; ret = misc_register(demo_dev->mdev); if (ret < 0) { printk(KERN_ERR "failed to register misc\n"); goto ERROR_MISC; } printk(KERN_INFO "demo char device init done\n"); return 0; ERROR_MISC: kfree(demo_dev->buffer); demo_dev->buffer = NULL; ERROR_MALLOC_BUFFER: kfree(demo_dev); demo_dev = NULL; ERROR_MALLOC_DEMODEV: return ret; } static void __exit demo_exit(void) { printk(KERN_INFO "Enter: %s\n", __func__); misc_deregister(demo_dev->mdev); kfree(demo_dev->buffer); demo_dev->buffer = NULL; kfree(demo_dev); demo_dev = NULL; printk(KERN_INFO "demo char device exit done\n"); } module_init(demo_init); module_exit(demo_exit); MODULE_AUTHOR("Qi Han"); MODULE_LICENSE("GPL");
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod misc_dev.ko [sudo] hq 的密碼: hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ls -al /dev/misc_dev crw------- 1 root root 10, 56 3月 10 12:12 /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "hq666" -> /dev/misc_dev hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev hq666 - hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ ./test write to cdev 10 bytes value = 1 value = 520 hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ dmesg [ 4161.949170] Enter: demo_init [ 4161.951759] demo char device init done [ 4213.359167] Enter: demo_open [ 4213.359182] Enter: demo_write [ 4213.359187] Enter: demo_release [ 4220.892937] Enter: demo_open [ 4220.892945] Enter: demo_read [ 4220.892974] Enter: demo_read [ 4220.892983] Enter: demo_release [ 4394.397552] Enter: demo_open [ 4394.397556] Enter: demo_write [ 4394.397648] cmd: getval [ 4394.397651] cmd: setval [ 4394.397652] cmd: getval [ 4394.397656] Enter: demo_release
/sys/class
文件夾下面查看misc設備,其中就包含了咱們剛纔建立的設備hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cd /sys hq@hq-virtual-machine:/sys$ ls block bus class dev devices firmware fs hypervisor kernel module power hq@hq-virtual-machine:/sys$ cd class/ hq@hq-virtual-machine:/sys/class$ ls ata_device devcoredump extcon input mmc_host ppdev regulator sound tty ata_link devfreq firmware intel_scu_ipc nd ppp remoteproc spi_host vc ata_port devfreq-event gpio iommu net pps rfkill spi_master vfio backlight dma graphics leds pci_bus printer rtc spi_slave virtio-ports bdi dma_heap hidraw lirc pci_epc ptp scsi_device spi_transport vtconsole block dmi hwmon mdio_bus phy pwm scsi_disk thermal wakeup bsg drm i2c-adapter mem powercap rapidio_port scsi_generic tpm watchdog dax drm_dp_aux_dev i2c-dev misc power_supply rc scsi_host tpmrm hq@hq-virtual-machine:/sys/class$ cd misc/ hq@hq-virtual-machine:/sys/class/misc$ ls agpgart cpu_dma_latency ecryptfs hpet lightnvm mcelog psaux snapshot udmabuf vfio vmci autofs device-mapper fuse hw_random loop-control misc_dev rfkill tun uinput vga_arbiter vsock hq@hq-virtual-machine:/sys/class/misc$ ls -l 總用量 0 lrwxrwxrwx 1 root root 0 3月 10 12:23 agpgart -> ../../devices/virtual/misc/agpgart lrwxrwxrwx 1 root root 0 3月 10 12:23 autofs -> ../../devices/virtual/misc/autofs lrwxrwxrwx 1 root root 0 3月 10 12:23 cpu_dma_latency -> ../../devices/virtual/misc/cpu_dma_latency lrwxrwxrwx 1 root root 0 3月 10 12:23 device-mapper -> ../../devices/virtual/misc/device-mapper lrwxrwxrwx 1 root root 0 3月 10 12:23 ecryptfs -> ../../devices/virtual/misc/ecryptfs lrwxrwxrwx 1 root root 0 3月 10 12:23 fuse -> ../../devices/virtual/misc/fuse lrwxrwxrwx 1 root root 0 3月 10 12:23 hpet -> ../../devices/virtual/misc/hpet lrwxrwxrwx 1 root root 0 3月 10 12:23 hw_random -> ../../devices/virtual/misc/hw_random lrwxrwxrwx 1 root root 0 3月 10 12:23 lightnvm -> ../../devices/virtual/misc/lightnvm lrwxrwxrwx 1 root root 0 3月 10 12:23 loop-control -> ../../devices/virtual/misc/loop-control lrwxrwxrwx 1 root root 0 3月 10 12:23 mcelog -> ../../devices/virtual/misc/mcelog lrwxrwxrwx 1 root root 0 3月 10 12:23 misc_dev -> ../../devices/virtual/misc/misc_dev lrwxrwxrwx 1 root root 0 3月 10 12:23 psaux -> ../../devices/virtual/misc/psaux lrwxrwxrwx 1 root root 0 3月 10 12:23 rfkill -> ../../devices/virtual/misc/rfkill lrwxrwxrwx 1 root root 0 3月 10 12:23 snapshot -> ../../devices/virtual/misc/snapshot lrwxrwxrwx 1 root root 0 3月 10 12:23 tun -> ../../devices/virtual/misc/tun lrwxrwxrwx 1 root root 0 3月 10 12:23 udmabuf -> ../../devices/virtual/misc/udmabuf lrwxrwxrwx 1 root root 0 3月 10 12:23 uinput -> ../../devices/virtual/misc/uinput lrwxrwxrwx 1 root root 0 3月 10 12:23 vfio -> ../../devices/virtual/misc/vfio lrwxrwxrwx 1 root root 0 3月 10 12:23 vga_arbiter -> ../../devices/virtual/misc/vga_arbiter lrwxrwxrwx 1 root root 0 3月 10 12:23 vmci -> ../../devices/virtual/misc/vmci lrwxrwxrwx 1 root root 0 3月 10 12:23 vsock -> ../../devices/virtual/misc/vsock hq@hq-virtual-machine:/sys/class/misc$ cd misc_dev hq@hq-virtual-machine:/sys/class/misc/misc_dev$ ls dev power subsystem uevent hq@hq-virtual-machine:/sys/class/misc/misc_dev$ cat uevent MAJOR=10 MINOR=56 DEVNAME=misc_dev hq@hq-virtual-machine:/sys/class/misc/misc_dev$