1、設備號
主設備號標識設備對應的驅動程序,次設備號由內核使用,用于確定設備文件所指的設備。
通過次設備號獲得一個指向內核設備的直接指針,也可將此設備號當作設備本地數組的索引。
設備編號用dev_t表示(Linux/types.h? 32位,其中12位表示主設備號,20位表示次設備號)。
由dev_t獲得主設備號或次設備號:MAJOR(dev_t dev); MINOR(dev_t dev)
已知主設備號和次設備號來獲取dev_t類型:MKDEV(int? major,? int? minor)
獲取一個或多個設備編號:int? register_chrdev_region(dev_t first,? unsigned int? count,? char? *name);(靜態分配,事先已知道設備號)
動態分配設備編號:int? alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);調用成功后dev會保存已分配的第一個編號。
釋放設備編號:void unregister_chrdev_region(dev_t first, unsigned int count);
接下來,驅動程序需要將設備編號和內部函數連接起來。
注:(下一步可嘗試采用動態分配設備號)
?
動態分配設備號缺點:不能預先創建設備節點(因為分配的設備號不能保證始終一致)。
2、文件操作file_operations:
這些操作將與設備編號連接起來。
__user:用于文檔,表明該指針是一個用戶空間地址。
主要成員:open, ?ioctl,? read,? write,? llseek
3、struct file結構? linux/fs.h文件描述符
每打開一個文件,內核就會創建一個對應的file結構,在open()時創建,同時會傳遞給在該文件上進行操作的所有函數(因為file結構中包含file_operations結構,而該結構包含了所有驅動操作的函數)。
內核中用filp作為執行file結構的指針。
主要成員:
Mode_t? f_mode;? loff_t? f_pos;? ?struct file_operations *f_pos;? void? private_data;?
4、inode結構
對單個文件只有一個inode,而可能有多個file(由于fork,dup操作)。
主要成員:
dev_t? i_rdev; 對表示設備文件的inode結構,該字段包含真正的設備編號。
struct cdev?*i_cdev;? ?該結構表示字符設備的內核的內部結構,當inode指向一個字符設備文件時,該字段包含指向struct cdev結構的指針。
從inode中獲取設備號: iminor(struct inode *inode);? imajor(inode);
5、字符設備注冊? /linux/cdev.h
內核使用struct cdev結構表示字符設備,所以在內核調用該設備操作之前,需要分配并注冊一個或者多個該結構。
注冊有兩種方式:
新方法:
1)定義字節的結構:
struct my_dev {
???????? struct? cdev? cdev; //此處如果定義的是指針類型,則需要申請分配內存
}my_dev;
//my_dev ->cdev = cdev_alloc();? //如果cdev是指針則需要這一步
my_dev->cdev.ops = &my_fops;
my_dev->cdev.owner = THIS_MODULE;
2)再調用cdev_init(struct cdev *cdev,? struct file_operations *fops);
3)調用cdev_add(struct cdev *dev,? dev_t num,? unsigned int count);
上面每一步都要判斷函數調用是否出錯。
舊辦法(老接口):
注冊:int register_chrdev();移除:int unregister_chrdev()
?
6、各操作函數實現
1)open?? int (*open) (struct? inode? *inode,? struct? file? *filp)
完成以下工作:傳入一個inode,創建一個file結構
n? 檢查設備特定錯誤(如未就緒);
n? 如果設備首次打開,則進行初始化;
n? 必要時更新f_op指針;
n? 分配并填寫filp->private_data;
注:inode結構是傳入的參數,對應一個特定的設備(這就是為什么要在/dev下mknodde原因),而file結構的filp是要修改的參數(傳出),對應該設備的一個文件描述符,也就是一個inode可能有多個file描述符,而每個描述符需要保存inode的信息,即存放在filp->private_data中。
2)release? int (*release) (struct inode *inode,? struct file *filp)
完成工作:傳入一個inode,釋放這個file結構
n? 釋放由open分配的、保存在filp->private_data中的內容;
n? 在最后一次close時關閉設備
dup 和fork都會在不調用open時創建新的file結構(對應同一個inode)。
注:并不是每個close調用都會調用release;只有真正釋放設備數據結構的close調用才會調用release。內核對每個file結構維護其被使用次數的計數器,無論是fork還是dup,都不會創建新的數據結構(只會有open創建),它們只是增加已有結構中的計數器而已。只有在file結構的計數為0時,close才會調用release。
3)read? ssize_t ?read(struct ?file? *filp,? char __user *buf, ?count,? loff_t *offp)
完成工作:傳入file,將count個字節數據寫入用戶地址buf,修改loff_t
由copy_to_user()實現
返回值說明:
n? 等于count:所請求的字節數讀取成功;
n? 返回值為正,但小于count:只讀取了部分數據;
n? 為0:已經達到文件尾;
n? 負值:出錯
4)write? ssize_t? write(struct ?file? *filp,? char? __user *buf,? count,? offp);
由copy_from_user()實現
返回值同上。
?
- 1)驅動代碼??
- Demo.h??
- #ifndef?_DEMO_H_??
- #define?_DEMO_H_??
- #include?<linux/ioctl.h>??
- /*Macros?to?help?debuging*/??
- #undef?PDEBUG??
- #ifdef?DEMO_DEBUG??
- ????#ifdef?__KERNEL__??
- ????????#define?PDEBUG(fmt,?args...)?printk(KERN_DEBUG?"DEMO:"?fmt,##?args)???
- ????#else??
- ????????#define?PDEBUG(fmt,?args...)?fprintf(stderr,?fmt,?##?args)??
- ????#endif??
- #else??
- #define?PDEBUG(fmt,?args...)???
- #endif??
- ??
- #define?DEMO_MAJOR?224??
- #define?DEMO_MINOR?0??
- #define?COMMAND1?1??
- #define?COMMAND2?2??
- ??
- struct?demo_dev?{??
- ????struct?cdev?cdev;??
- };??
- ??
- ssize_t?demo_read(struct?file?*filp,?char?__user?*buf,?size_t?count,?loff_t?*f_pos);??
- ssize_t?demo_write(struct?file?*filp,?const?char?__user?*buf,?size_t?count,?loff_t?*f_pos);??
- loff_t?demo_llseek(struct?file?*filp,?loff_t?off,?int?whence);??
- int?demo_ioctl(struct?inode?*inode,?struct?file?*filp,?unsigned?int?cmd,?unsigned?long?arg);??
- ??
- #endif??
- demo.c??
- #include?<linux/module.h>??
- #include?<linux/kernel.h>??
- #include?<linux/fs.h>??
- #include?<linux/errno.h>??
- #include?<linux/types.h>??
- #include?<linux/fcntl.h>??
- #include?<linux/cdev.h>??
- #include?<linux/version.h>??
- #include?<linux/vmalloc.h>??
- #include?<linux/ctype.h>??
- #include?<linux/pagemap.h>??
- #include?"demo.h"??
- ??
- MODULE_AUTHOR("Yangjin");??
- MODULE_LICENSE("Dual?BSD/GPL");??
- ??
- struct?demo_dev?*demo_devices;??
- ??
- static?unsigned?char?demo_inc?=?0;//全局變量,每次只能打開一個設備??
- ??
- static?u8?demo_buffer[256];??
- ??
- int?demo_open(struct?inode?*inode,?struct?file?*filp)??
- {??
- ????struct?demo_dev?*dev;??
- ??????
- ????if?(demo_inc?>?0)?return?-ERESTARTSYS;??
- ????demo_inc++;??
- ????dev?=?container_of(inode->i_cdev,?struct?demo_dev,?cdev);??
- ????filp->private_data?=?dev;??
- ??
- ????return?0;??
- }??
- ??
- int?demo_release(struct?inode?*inode,?struct?file?*filp)??
- {?????
- ????demo_inc--;??
- ????return?0;??
- }??
- ??
- ssize_t?demo_read(struct?file?*filp,?char?__user?*buf,?size_t?count,?loff_t?*f_pos)??
- {??
- ????int?result;??
- ????loff_t?pos?=?*f_pos;?//pos:?offset??
- ??
- ????if?(pos?>=?256)?{??
- ????????result?=?0;??
- ????????goto?out;????????????????????????????????????????????????????????????????????????????????????????????????
- ????}??
- ????if?(count?>?(256?-?pos))??
- ????????count?=?256?-?pos;??
- ????pos?+=?count;??
- ??
- ????if?(copy_to_user(buf,?demo_buffer+*f_pos,?count))?{??
- ????????count?=?-EFAULT;??
- ????????goto?out;?????
- ????}??
- ??????
- ????*f_pos?=?pos;??
- out:??
- ????return?count;??
- }??
- ??
- ssize_t??demo_write(struct?file?*filp,?const?char?__user?*buf,?size_t?count,?loff_t?*f_pos)??
- {??
- ????ssize_t?retval?=?-ENOMEM;??
- ????loff_t?pos?=?*f_pos;??
- ??
- ????if?(pos?>?256)??
- ????????goto?out;??
- ????if?(count?>?(256?-?pos))???
- ????????count?=?256?-?pos;????
- ????pos?+=?count;??
- ????if?(copy_from_user(demo_buffer+*f_pos,?buf,?count))?{??
- ????????retval?=?-EFAULT;??
- ????????goto?out;?????
- ????}??
- ??????
- ????*f_pos?=?pos;??
- ????retval?=?count;??
- out:??
- ????return?retval;??
- }??
- ??
- int??demo_ioctl(struct?inode?*inode,?struct?file?*filp,?unsigned?int?cmd,?unsigned?long?arg)??
- {??
- ????if?(cmd?==?COMMAND1)?{??
- ????????printk("ioctl?command?1?successfully\n");??
- ????????return?0;?????
- ????}??
- ????if?(cmd?==?COMMAND2)?{??
- ????????printk("ioctl?command?2?successfully\n");??
- ????????return?0;?????
- ????}??
- ????printk("ioctl?error\n");??
- ????return?-EFAULT;??
- }??
- ??
- loff_t?demo_llseek(struct?file?*filp,?loff_t?off,?int?whence)??
- {??
- ????loff_t?pos;??
- ??????
- ????pos?=?filp->f_pos;??
- ????switch?(whence)?{??
- ????case?0:??
- ????????pos?=?off;??
- ????????break;??
- ????case?1:??
- ????????pos?+=?off;??
- ????????break;??
- ????case?2:??
- ????default:??
- ????????return?-EINVAL;???
- ????}??
- ??????
- ????if?((pos?>?256)?||?(pos?<?0))??
- ????????return?-EINVAL;??
- ??????
- ????return?filp->f_pos?=?pos;??
- }??
- ??
- struct?file_operations?demo_fops?=?{??
- ????.owner?=?THIS_MODULE,??
- ????.llseek?=?demo_llseek,??
- ????.read?=?demo_read,??
- ????.write?=?demo_write,??
- ????.ioctl?=?demo_ioctl,??
- ????.open?=?demo_open,??
- ????.release?=?demo_release,??
- };??
- ??
- void?demo_cleanup_module(void)??
- {??
- ????dev_t?devno?=?MKDEV(DEMO_MAJOR,?DEMO_MINOR);??
- ??????
- ????if?(demo_devices)?{??
- ????????cdev_del(&demo_devices->cdev);??
- ????????kfree(demo_devices);??
- ????}??
- ????unregister_chrdev_region(devno,?1);??
- }??
- ??
- Init?module流程:??
- 1)注冊設備號MKDEV;??
- 2)注冊設備驅動程序,即初始化cdev結構(嵌入到demo_devices結構中)??
- int?demo_init_module(void)??
- {??
- ????int?result;??
- ????dev_t?dev?=?0;??
- ??????
- ????dev?=?MKDEV(DEMO_MAJOR,?DEMO_MINOR);??
- ????result?=?register_chrdev_region(dev,?1,?"DEMO");??
- ????if?(result?<?0)?{??
- ????????printk(KERN_WARNING?"DEMO:?can't?get?major?%d\n",?DEMO_MAJOR);??
- ????????return?result;??
- ????}??
- ????demo_devices?=?kmalloc(sizeof(struct?demo_dev),?GFP_KERNEL);??
- ????if?(!demo_devices)?{??
- ????????result?=?-ENOMEM;??
- ????????goto?fail;??
- ????}??
- ????memset(demo_devices,?0,?sizeof(struct?demo_dev));??
- ????cdev_init(&demo_devices->cdev,?&demo_fops);????
- demo_devices->cdev.owner?=?THIS_MODULE;??
- ????demo_devices->cdev.ops?=?&demo_fops;?//將創建的字符設備與file_operations中各函數操作連接起來??
- ??
- ????result?=?cdev_add(&demo_devices->cdev,?dev,?1);??
- ????if?(result)?{??
- ????????printk(KERN_NOTICE?"error?%d?adding?demo\n",?result);??
- ????????goto?fail;??
- ????}??
- ????return?0;??
- fail:??
- ????demo_cleanup_module();??
- ????return?result;??
- }??
- ??
- module_init(demo_init_module);??
- module_exit(demo_cleanup_module);??
?
2)加載驅動insmod demo.ko,再使用lsmod或cat /proc/modules查看驅動是否安裝;
3)創建設備節點:mknod? /dev/yangjin c 224 0;注意:此處的節點設備號要與驅動程序中的注冊的設備號相同。
4)再編寫應用程序測試代碼:
用戶測試代碼:
- #include?<sys/types.h>??
- #include?<unistd.h>??
- #include?<fcntl.h>??
- #include?<linux/rtc.h>??
- #include?<linux/ioctl.h>??
- #include?<stdio.h>??
- #include?<stdlib.h>??
- ??
- #define?COMMAND1?1??
- #define?COMMAND2?2??
- ??
- int?main()??
- {??
- ????int?fd;??
- ????int?i;??
- ????char?data[256]?=?{0};??
- ????int?retval;??
- ??????
- ????fd?=?open("/dev/yangjin",?O_RDWR);??
- ????if?(fd?==?1)?{??
- ????????perror("open?error\n");??
- ????????exit(-1);??
- ????}??
- ????printf("open?/dev/yangjin?successfully\n");??
- ????retval?=?ioctl(fd,?COMMAND1,?0);??
- ????if?(retval?==?-1)?{??
- ????????perror("ioctl?error\n");??
- ????????exit(-1);??
- ????}??
- ????printf("ioctl?command?1?successfully\n");??
- ????retval?=?write(fd,?"yangjin",?7);??
- ????if?(retval?==?-1)?{??
- ????????perror("write?error\n");??
- ????????exit(-1);??
- ????}??
- ????retval?=?lseek(fd,?0,?0);??
- ????if?(retval?==?-1)?{??
- ????????perror("lseek?error\n");??
- ????????exit(-1);??
- ????}??
- ????retval?=?read(fd,?data,?10);??
- ????if?(retval?==?-1)?{??
- ????????perror("read?error\n");??
- ????????exit(-1);??
- ????}??
- ????printf("read?successfully:?%s\n",?data);??
- ????close(fd);??
- ????return?0;??
- }??