字符設(shè)備是Linux三大設(shè)備之一(另外兩種是塊設(shè)備,網(wǎng)絡(luò)設(shè)備),字符設(shè)備就是字節(jié)流形式通訊的I/O設(shè)備,絕大部分設(shè)備都是字符設(shè)備,常見的字符設(shè)備包括鼠標(biāo)、鍵盤、顯示器、串口等等,當(dāng)我們執(zhí)行l(wèi)s -l /dev的時(shí)候,就能看到大量的設(shè)備文件,c就是字符設(shè)備,b就是塊設(shè)備,網(wǎng)絡(luò)設(shè)備沒(méi)有對(duì)應(yīng)的設(shè)備文件。編寫一個(gè)外部模塊的字符設(shè)備驅(qū)動(dòng),除了要實(shí)現(xiàn)編寫一個(gè)模塊所需要的代碼之外,還需要編寫作為一個(gè)字符設(shè)備的代碼。
驅(qū)動(dòng)模型
Linux一切皆文件,那么作為一個(gè)設(shè)備文件,它的操作方法接口封裝在struct file_operations,當(dāng)我們寫一個(gè)驅(qū)動(dòng)的時(shí)候,一定要實(shí)現(xiàn)相應(yīng)的接口,這樣才能使這個(gè)驅(qū)動(dòng)可用,Linux的內(nèi)核中大量使用"注冊(cè)+回調(diào)"機(jī)制進(jìn)行驅(qū)動(dòng)程序的編寫,所謂注冊(cè)回調(diào),簡(jiǎn)單的理解,就是當(dāng)我們open一個(gè)設(shè)備文件的時(shí)候,其實(shí)是通過(guò)VFS找到相應(yīng)的inode,并執(zhí)行此前創(chuàng)建這個(gè)設(shè)備文件時(shí)注冊(cè)在inode中的open函數(shù),其他函數(shù)也是如此,所以,為了讓我們寫的驅(qū)動(dòng)能夠正常的被應(yīng)用程序操作,首先要做的就是實(shí)現(xiàn)相應(yīng)的方法,然后再創(chuàng)建相應(yīng)的設(shè)備文件。
#include //for struct cdev#include //for struct file#include //for copy_to_user#include //for error numberstatic int ma = 0;static int mi = 0;const int count = 3;/* 準(zhǔn)備操作方法集 *//* struct file_operations { struct module *owner; //THIS_MODULE //讀設(shè)備 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //寫設(shè)備 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //映射內(nèi)核空間到用戶空間 int (*mmap) (struct file *, struct vm_area_struct *); //讀寫設(shè)備參數(shù)、讀設(shè)備狀態(tài)、控制設(shè)備 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); //打開設(shè)備 int (*open) (struct inode *, struct file *); //關(guān)閉設(shè)備 int (*release) (struct inode *, struct file *); //刷新設(shè)備 int (*flush) (struct file *, fl_owner_t id); //文件定位 loff_t (*llseek) (struct file *, loff_t, int); //異步通知 int (*fasync) (int, struct file *, int); //POLL機(jī)制 unsigned int (*poll) (struct file *, struct poll_table_struct *); 。。。};*/ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset){ return 0;}struct file fops = { .owner = THIS_MODULE, .read = myread, ...};/* 字符設(shè)備對(duì)象類型 struct cdev { struct kobject kobj; struct module *owner; //模塊所有者(THIS_MODULE),用于模塊計(jì)數(shù) const struct file_operations *ops; //操作方法集(分工:打開、關(guān)閉、讀/寫、...) struct list_head list; dev_t dev; //設(shè)備號(hào)(第一個(gè)) unsigned int count; //設(shè)備數(shù)量};*/static int __init chrdev_init(void){ ... /* 構(gòu)造cdev設(shè)備對(duì)象 */ struct cdev *cdev_alloc(void); /* 初始化cdev設(shè)備對(duì)象 */ void cdev_init(struct cdev*, const struct file_opeartions*); /* 申請(qǐng)?jiān)O(shè)備號(hào),靜態(tài)or動(dòng)態(tài)*/ /* 為字符設(shè)備靜態(tài)申請(qǐng)第一個(gè)設(shè)備號(hào) */ int register_chrdev_region(dev_t from, unsigned count, const char* name); /* 為字符設(shè)備動(dòng)態(tài)申請(qǐng)第一個(gè)設(shè)備號(hào) */ int alloc_chrdev_region(dev_t* dev, unsigned baseminor, unsigned count, const char* name); ma = MAJOR(dev) //從dev_t數(shù)據(jù)中得到主設(shè)備號(hào) mi = MINOR(dev) //從dev_t數(shù)據(jù)中得到次設(shè)備號(hào) MKDEV(ma,1) //將主設(shè)備號(hào)和次設(shè)備號(hào)組合成設(shè)備號(hào),多用于批量創(chuàng)建/刪除設(shè)備文件 /* 注冊(cè)字符設(shè)備對(duì)象cdev到內(nèi)核 */ int cdev_add(struct cdev* , dev_t, unsigned); ...}static void __exit chrdev_exit(void){ ... /* cdev_del()、cdev_put()二選一 */ /* 從內(nèi)核注銷cdev設(shè)備對(duì)象 */ void cdev_del(struct cdev* ); /* 從內(nèi)核注銷cdev設(shè)備對(duì)象 */ void cdev_put(stuct cdev *); /* 回收設(shè)備號(hào) */ void unregister_chrdev_region(dev_t from, unsigned count); ...}
羅嗦一句,如果使用靜態(tài)申請(qǐng)?jiān)O(shè)備號(hào),那么最大的問(wèn)題就是不要與已知的設(shè)備號(hào)相沖突,內(nèi)核在文檔"Documentation/devices.txt"中已經(jīng)注明了哪些主設(shè)備號(hào)被使用了,從中可以看出,在2^12個(gè)主設(shè)備號(hào)中,我們能夠使用的范圍是240-255以及261-2^12-1的部分,這也可以解釋為什么我們動(dòng)態(tài)申請(qǐng)的時(shí)候,設(shè)備號(hào)經(jīng)常是250的原因。此外,通過(guò)這個(gè)文件,我們也可以看出,"主設(shè)備號(hào)表征一類設(shè)備",但是字符/塊設(shè)備本身就可以被分為好多類,所以內(nèi)核給他們每一類都分配了主設(shè)備號(hào)。
實(shí)現(xiàn)read,write
Linux下各個(gè)進(jìn)程都有自己獨(dú)立的進(jìn)程空間,即使是將內(nèi)核的數(shù)據(jù)映射到用戶進(jìn)程,該數(shù)據(jù)的PID也會(huì)自動(dòng)轉(zhuǎn)變?yōu)樵撚脩暨M(jìn)程的PID,由于這種機(jī)制的存在,我們不能直接將數(shù)據(jù)從內(nèi)核空間和用戶空間進(jìn)行拷貝,而需要專門的拷貝數(shù)據(jù)函數(shù)/宏:
long copy_from_user(void *to, const void __user * from, unsigned long n)long copy_to_user(void __user *to, const void *from, unsigned long n)
這兩個(gè)函數(shù)可以將內(nèi)核空間的數(shù)據(jù)拷貝到回調(diào)該函數(shù)的用戶進(jìn)程的用戶進(jìn)程空間,有了這兩個(gè)函數(shù),內(nèi)核中的read,write就可以實(shí)現(xiàn)內(nèi)核空間和用戶空間的數(shù)據(jù)拷貝。
ssize_t myread(struct file *filep, char __user * user_buf, size_t size, loff_t* offset){ long ret = 0; size = size > MAX_KBUF?MAX_KBUF:size; if(copy_to_user(user_buf, kbuf,size) return -EAGAIN; } return 0;}
實(shí)現(xiàn)ioctl
ioctl是Linux專門為用戶層控制設(shè)備設(shè)計(jì)的系統(tǒng)調(diào)用接口,這個(gè)接口具有極大的靈活性,我們的設(shè)備打算讓用戶通過(guò)哪些命令實(shí)現(xiàn)哪些功能,都可以通過(guò)它來(lái)實(shí)現(xiàn),ioctl在操作方法集中對(duì)應(yīng)的函數(shù)指針是long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);,其中的命令和參數(shù)完全由驅(qū)動(dòng)指定,通常命令會(huì)寫在一個(gè)頭文件中以供應(yīng)用層和驅(qū)動(dòng)層遵守同樣的通信協(xié)議,Linux建議如圖所示的方式定義ioctl()命令
設(shè)備類型 序列號(hào) 方向 數(shù)據(jù)尺寸8bit 8bit 2bit 13/14bit
設(shè)備類型字段為一個(gè)幻數(shù),可以是0~0xff之間的數(shù),內(nèi)核中的"ioctl-number.txt"給出了一個(gè)推薦的和已經(jīng)被使用的幻數(shù)(但是已經(jīng)好久沒(méi)人維護(hù)了),新設(shè)備驅(qū)動(dòng)定義幻數(shù)的時(shí)候要避免與其沖突。
序列號(hào)字段表示當(dāng)前命令是整個(gè)ioctl命令中的第幾個(gè),從1開始計(jì)數(shù)。
方向字段為2bit,表示數(shù)據(jù)的傳輸方向,可能的值是:_IOC_NONE,_IOC_READ,_IOC_WRITE和_IOC_READ|_IOC_WRITE。
數(shù)據(jù)尺寸字段表示涉及的用戶數(shù)據(jù)的大小,這個(gè)成員的寬度依賴于體系結(jié)構(gòu),通常是13或14位。
內(nèi)核還定義了_IO(),_IOR(),_IOW(),_IOWR()這4個(gè)宏來(lái)輔助生成這種格式的命令。這幾個(gè)宏的作用是根據(jù)傳入的type(設(shè)備類型字段),nr(序列號(hào)字段)和size(數(shù)據(jù)長(zhǎng)度字段)和方向字段移位組合生成命令碼。
內(nèi)核中還預(yù)定義了一些I/O控制命令,如果某設(shè)備驅(qū)動(dòng)中包含了與預(yù)定義命令一樣的命令碼,這些命令會(huì)被當(dāng)做預(yù)定義命令被內(nèi)核處理而不是被設(shè)備驅(qū)動(dòng)處理,有如下4種:
FIOCLEX:即file ioctl close on exec 對(duì)文件設(shè)置專用的標(biāo)志,通知內(nèi)核當(dāng)exec()系統(tǒng)帶哦用發(fā)生時(shí)自動(dòng)關(guān)閉打開的文件
FIONCLEX:即file ioctl not close on exec,清除由FIOCLEX設(shè)置的標(biāo)志
FIOQSIZE:獲得一個(gè)文件或目錄的大小,當(dāng)用于設(shè)備文件時(shí),返回一個(gè)ENOTTY錯(cuò)誤
FIONBIO:即file ioctl non-blocking I/O 這個(gè)調(diào)用修改flip->f_flags中的O_NONBLOCK標(biāo)志
實(shí)例
//mycmd.h...#include #define CMDT 'A'#define KARG_SIZE 36struct karg{ int kval; char kbuf[KARG_SIZE];};#define CMD_OFF _IO(CMDT,0)#define CMD_ON _IO(CMDT,1)#define CMD_R _IOR(CMDT,2,struct karg)#define CMD_W _IOW(CMDT,3,struct karg)...
//chrdev.cstatic long demo_ioctl(struct file *filp, unsigned int cmd, unsigned long arg){ static struct karg karg = { .kval = 0, .kbuf = {0}, }; struct karg *usr_arg; switch(cmd){ case CMD_ON: /* 開燈 */ break; case CMD_OFF: /* 關(guān)燈 */ break; case CMD_R: if(_IOC_SIZE(cmd) != sizeof(karg)){ return -EINVAL; } usr_arg = (struct karg *)arg; if(copy_to_user(usr_arg, &karg, sizeof(karg))){ return -EAGAIN; } break; case CMD_W: if(_IOC_SIZE(cmd) != sizeof(karg)){ return -EINVAL; } usr_arg = (struct karg *)arg; if(copy_from_user(&karg, usr_arg, sizeof(karg))){ return -EAGAIN; } break; default: ; }; return 0;}
創(chuàng)建設(shè)備文件
插入的設(shè)備模塊,我們就可以使用cat /proc/devices命令查看當(dāng)前系統(tǒng)注冊(cè)的設(shè)備,但是我們還沒(méi)有創(chuàng)建相應(yīng)的設(shè)備文件,用戶也就不能通過(guò)文件訪問(wèn)這個(gè)設(shè)備。設(shè)備文件的inode應(yīng)該是包含了這個(gè)設(shè)備的設(shè)備號(hào),操作方法集指針等信息,這樣我們就可以通過(guò)設(shè)備文件找到相應(yīng)的inode進(jìn)而訪問(wèn)設(shè)備。創(chuàng)建設(shè)備文件的方法有兩種,手動(dòng)創(chuàng)建或自動(dòng)創(chuàng)建,手動(dòng)創(chuàng)建設(shè)備文件就是使用mknod /dev/xxx 設(shè)備類型 主設(shè)備號(hào) 次設(shè)備號(hào)的命令創(chuàng)建,所以首先需要使用cat /proc/devices查看設(shè)備的主設(shè)備號(hào)并通過(guò)源碼找到設(shè)備的次設(shè)備號(hào),需要注意的是,理論上設(shè)備文件可以放置在任何文件加夾,但是放到"/dev"才符合Linux的設(shè)備管理機(jī)制,這里面的devtmpfs是專門設(shè)計(jì)用來(lái)管理設(shè)備文件的文件系統(tǒng)。設(shè)備文件創(chuàng)建好之后就會(huì)和創(chuàng)建時(shí)指定的設(shè)備綁定,即使設(shè)備已經(jīng)被卸載了,如要?jiǎng)h除設(shè)備文件,只需要像刪除普通文件一樣rm即可。理論上模塊名(lsmod),設(shè)備名(/proc/devices),設(shè)備文件名(/dev)并沒(méi)有什么關(guān)系,完全可以不一樣,但是原則上還是建議將三者進(jìn)行統(tǒng)一,便于管理。
除了使用蹩腳的手動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn)的方式,我們還可以在設(shè)備源碼中使用相應(yīng)的措施使設(shè)備一旦被加載就自動(dòng)創(chuàng)建設(shè)備文件,自動(dòng)創(chuàng)建設(shè)備文件需要我們?cè)诰幾g內(nèi)核的時(shí)候或制作根文件系統(tǒng)的時(shí)候就好相應(yīng)的配置:
Device Drivers ---> Generic Driver Options ---> [*]Maintain a devtmpfs filesystem to mount at /dev [*] Automount devtmpfs at /dev,after the kernel mounted the rootfs
OR
制作根文件系統(tǒng)的啟動(dòng)腳本寫入
mount -t sysfs none sysfs /sysmdev -s //udev也行
有了這些準(zhǔn)備,只需要導(dǎo)出相應(yīng)的設(shè)備信息到"/sys"就可以按照我們的要求自動(dòng)創(chuàng)建設(shè)備文件。內(nèi)核給我們提供了相關(guān)的API
class_create(owner,name);struct device *device_create_vargs(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, va_list vargs);void class_destroy(struct class *cls); void device_destroy(struct class *cls, dev_t devt);
有了這幾個(gè)函數(shù),我們就可以在設(shè)備的xxx_init()和xxx_exit()中分別填寫以下的代碼就可以實(shí)現(xiàn)自動(dòng)的創(chuàng)建刪除設(shè)備文件
/* 在/sys中導(dǎo)出設(shè)備類信息 */ cls = class_create(THIS_MODULE,DEV_NAME); /* 在cls指向的類中創(chuàng)建一組(個(gè))設(shè)備文件 */ for(i= minor;i<(minor+cnt);i++){ devp = device_create(cls,NULL,MKDEV(major,i),NULL,"%s%d",DEV_NAME,i); }
/* 在cls指向的類中刪除一組(個(gè))設(shè)備文件 */ for(i= minor;i<(minor+cnt);i++){ device_destroy(cls,MKDEV(major,i)); } /* 在/sys中刪除設(shè)備類信息 */ class_destroy(cls); //一定要先卸載device再卸載class
完成了這些工作,一個(gè)簡(jiǎn)單的字符設(shè)備驅(qū)動(dòng)就搭建完成了,現(xiàn)在就可以寫一個(gè)用戶程序進(jìn)行測(cè)試了^ - ^
?
評(píng)論
查看更多