Linux字符設備簡單示例
1. Linux字符設備是一種按字節來訪問的設備,字符驅動則負責驅動字符設備,這樣的驅動通常實現open、close、read和write系統調用。例如:串口、Led、按鍵等。
2. 通過字符設備文件(/dev/),應用程序可以使用相應的字符設備驅動來控制字符設備
3. 創建字符設備文件的方法一般有兩種
(1)使用命令mknod : mknod /dev/文件名 c 主設備號 次設備號 (查看主設備號:cat /proc/devices)
(2)使用函數在驅動程序中創建
4. 字符設備通用設計模型
5. 在任何一種驅動模型中,設備都會用內核中的一種結構來描述。字符設備在內核中使用struct cdev來描述
struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops; //設備操作函數集 struct list_head list; dev_t dev; //設備號 unsigned int count; //設備數 };
6. Linux內核中使用dev_t類型來定義設備號,dev_t其實質為32位unsigned int類型,其中高12位為主設備號,低20位為此設備號。
(1)MKDEV(主設備號,此設備號)
(2)MAJOR(dev_t dev)
(3)MINOR(dev_t dev)
註:字符設備文件與字符設備驅動是通過主設備號建立對應關系;驅動程序用此設備號來區分同類型的設備
7. 設備號的申請與註銷
(1)靜態申請:開發者自己選擇一個數字作為主設備號,通過函數 register_chardev_region 向內核申請
(2)動態分配:使用 alloc_chrdev_region 由內核分配一個可用的主設備號(推薦使用)
(3)不論使用何種方法分配設備號,都應該在驅動退出時,使用 unregister_chrdev_region 函數釋放這些設備
8. 操作函數集:struct file_operations是一個函數指針的集合,定義能在設備上進行的操作。
9. 字符設備描述結構的分配、註冊與註銷
(1)cdev變量的定義可以采用靜態和動態兩種方法
* 靜態分配:struct cdev mdev;
* 動態分配:struct cdev* pdev = cdev_alloc();(可以通過命令:cat /proc/devices查看主設備號)
(2)cdev變量的初始化使用cdev_init()函數來完成
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
cdev: 待初始化的cdev結構
fops: 設備對應的操作函數集
(3)字符設備的註冊使用cdev_add()函數來完成
(4)字符設備的註銷使用cdev_del()函數來完成
10. Linux驅動中幾個重要的數據結構
(1)在Linux系統中,每一個打開的文件,在內核中都會關聯一個struct file結構,它是由內核在打開文件時創建,在文件關閉後釋放。
struct file結構中的重要成員
* struct file_operations* f_op; //文件操作函數集
* loff_t f_pos; //文件讀寫指針
(2)每一個存在於文件系統中的文件都會關聯一個inode結構,該結構主要用來記錄文件物理上的信息。因此,它和代表打開文件的file結構是不同的,一個文件沒有被打開時不會關聯file結構,但是會關聯一個inode結構(存於磁盤,操作文件時在內存中建立相應的映射結構)
註:inode用於存儲文件的元信息(除了文件名的所有信心),中文譯名索引節點
11. 設備操作:struct file_operations
struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 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); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, 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 **); };
(1)open: int (*open) (struct inode *, struct file *);(打開設備,響應open系統調用)
open方法是驅動程序用來為以後的操作完成初始化準備工作的。在大部分驅動程序紅,open主要完成以下工作:
* 標明次設備號
* 啟動設備
(2)release: int (*release) (struct inode *, struct file *);(關閉設備,響應close系統調用)
(3)llseek: loff_t (*llseek) (struct file *, loff_t, int);(重定位讀寫指針,響應lseek系統調用)
(4)read:ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);(從設備讀取數據,響應read系統調用)
① read設備方法通常完成兩件事情
* 從設備中讀取數據(屬於硬件訪問類操作)
* 將讀取到的數據返回給應用程序
② ssize_t (*read) (struct file *filp, char __user *buff, size_t count, loff_t *offp)
filp:與字符設備文件關聯的file結構,由內核創建
buff:從設備文件讀取到的數據,需要保存到的位置。由read系統調用提供該參數
count:請求傳輸的數據量,由read系統調用提供該參數
offp:文件的讀寫位置,由內核從file結構中取出後,傳遞進來
③ buff參數是來源於用戶空間的指針,這類指針都不能被內核代碼直接引用,必須使用專門的函數
int copy_from_user(void *to, const void __user *from, int n) int copy_to_user(void __user *to, const void *from, int n)
(5)write:ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);(向設備寫入數據,響應write系統調用)
① write設備方法通常完成2件事情
* 從應用程序提供的地址中取出數據
* 將數據寫入設備(屬於硬件訪問類操作)
② 其參數類似於read
12. 字符設備簡單示例
①驅動程序 MemDev.c
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/uaccess.h> #include <linux/slab.h> /* We suppose this is the two device‘s registers */ int dev1_registers[5]; int dev2_registers[5]; struct cdev cdev; dev_t devno; /*文件打開函數*/ int mem_open(struct inode *inode, struct file *filp) { /*獲取次設備號*/ int num = MINOR(inode->i_rdev); if (num == 0) filp->private_data = dev1_registers; else if(num == 1) filp->private_data = dev2_registers; else return -ENODEV; //無效的次設備號 return 0; } /*文件釋放函數*/ int mem_release(struct inode *inode, struct file *filp) { return 0; } /*讀函數*/ static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; int *register_addr = filp->private_data; /*獲取設備的寄存器基地址*/ /*判斷讀位置是否有效*/ if (p >= 5 * sizeof(int)) return 0; if (count > 5 * sizeof(int) - p) count = 5 * sizeof(int) - p; /*讀數據到用戶空間*/ if (copy_to_user(buf, register_addr + p, count)) { ret = -EFAULT; } else { *ppos += count; ret = count; } return ret; } /*寫函數*/ static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) { unsigned long p = *ppos; unsigned int count = size; int ret = 0; int *register_addr = filp->private_data; /*獲取設備的寄存器地址*/ /*分析和獲取有效的寫長度*/ if (p >= 5*sizeof(int)) return 0; if (count > 5 * sizeof(int) - p) count = 5 * sizeof(int) - p; /*從用戶空間寫入數據*/ if (copy_from_user(register_addr + p, buf, count)) ret = -EFAULT; else { *ppos += count; ret = count; } return ret; } /* seek文件定位函數 */ static loff_t mem_llseek(struct file *filp, loff_t offset, int whence) { loff_t newpos; switch(whence) { case SEEK_SET: newpos = offset; break; case SEEK_CUR: newpos = filp->f_pos + offset; break; case SEEK_END: newpos = 5 * sizeof(int) - 1 + offset; break; default: return -EINVAL; } if ((newpos < 0) || (newpos > 5 * sizeof(int))) return -EINVAL; filp->f_pos = newpos; return newpos; } /*文件操作結構體*/ static const struct file_operations mem_fops = { .open = mem_open, .read = mem_read, .write = mem_write, .llseek = mem_llseek, .release = mem_release, }; /*設備驅動模塊加載函數*/ static int memdev_init(void) { /*初始化cdev結構*/ cdev_init(&cdev, &mem_fops); /* 註冊字符設備 */ alloc_chrdev_region(&devno, 0, 2, "memdev"); cdev_add(&cdev, devno, 2); } /*模塊卸載函數*/ static void memdev_exit(void) { cdev_del(&cdev); /*註銷設備*/ unregister_chrdev_region(devno, 2); /*釋放設備號*/ } MODULE_LICENSE("GPL"); module_init(memdev_init); module_exit(memdev_exit);
②測試代碼MemWrite.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { int fd = 0; int src0[] = {0, 1, 2, 3, 4}; int src1[] = {10, 11, 12, 13, 14}; /*打開設備文件*/ fd = open("/dev/memdev0", O_RDWR); /*寫入數據*/ write(fd, src0, sizeof(src0)); /*關閉設備*/ close(fd); fd = open("/dev/memdev1", O_RDWR); /*寫入數據*/ write(fd, src1, sizeof(src1)); /*關閉設備*/ close(fd); return 0; }
③測試代碼MemRead.c
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main() { int fd = 0; int dst = 0; /*打開設備文件*/ fd = open("/dev/memdev0", O_RDWR); lseek(fd, 2, SEEK_SET); /*寫入數據*/ read(fd, &dst, sizeof(int)); printf("dst0 is %d\n", dst); /*關閉設備*/ close(fd); /*打開設備文件*/ fd = open("/dev/memdev1", O_RDWR); lseek(fd, 3, SEEK_SET); /*寫入數據*/ read(fd, &dst, sizeof(int)); printf("dst1 is %d\n", dst); /*關閉設備*/ close(fd); return 0; }
④ 測試步驟
(1)安裝驅動模塊:insmod MemDev.ko
(2)查看主設備號:cat /proc/devices(查找memdev對應的主設備號)
(3)創建設備文件:mknod /dev/memdev0 c 主設備號 0
(4)運行測試代碼進行測試
Linux字符設備簡單示例