驅動程序就是向下控制硬件,向上提供接口,這里的向上提供的接口最終對應到應用層有三種方式:設備文件,/proc,/sys,其中最常用的就是使用設備文件,而Linux設備中用的最多的就是字符設備,本文就以字符設備為例來分析創建并打開一個字符設備的文件內部機制。
struct inode
Linux中一切皆文件,當我們在Linux中創建一個文件時,就會在相應的文件系統創建一個inode與之對應,文件實體和文件的inode是一一對應的,創建好一個inode會存在存儲器中,第一次open就會將inode在內存中有一個備份,同一個文件被多次打開并不會產生多個inode,當所有被打開的文件都被close之后,inode在內存中的實例才會被釋放。既然如此,當我們使用mknod(或其他方法)創建一個設備文件時,也會在文件系統中創建一個inode,這個inode和其他的inode一樣,用來存儲關于這個文件的靜態信息(不變的信息),包括這個設備文件對應的設備號,文件的路徑以及對應的驅動對象etc。inode作為VFS四大對象之一,在驅動開發中很少需要自己進行填充,更多的是在open()方法中進行查看并根據需要填充我們的file結構。
對于不同的文件類型,inode被填充的成員內容也會有所不同,以創建字符設備為例,我們知道,add_chrdev_region其實是把一個驅動對象和一個(一組)設備號聯系到一起。而創建設備文件,其實是把設備文件和設備號聯系到一起。至此,這三者就被綁定在一起了。這樣,內核就有能力創建一個struct inode實例了,下面是4.8.5內核中的inode。這個inode是VFS的inode,是最具體文件系統的inode的進一步封裝,也是驅動開發中關心的inode,針對具體的文件系統,還有struct ext2_inode_info 等結構。
//include/linux/fs.h 596 /* 597 * Keep mostly read-only and often accessed (especially for 598 * the RCU path lookup and 'stat' data) fields at the beginning 599 * of the 'struct inode' 600 */ 601 struct inode { 602 umode_t i_mode; 603 unsigned short i_opflags; 604 kuid_t i_uid; 605 kgid_t i_gid; 606 unsigned int i_flags; 607 608 #ifdef CONFIG_FS_POSIX_ACL 609 struct posix_acl *i_acl; 610 struct posix_acl *i_default_acl; 611 #endif 612 613 const struct inode_operations *i_op; 614 struct super_block *i_sb; 615 struct address_space *i_mapping; 616 617 #ifdef CONFIG_SECURITY 618 void *i_security; 619 #endif 620 621 /* Stat data, not accessed from path walking */ 622 unsigned long i_ino; 623 /* 624 * Filesystems may only read i_nlink directly. They shall use the 625 * following functions for modification: 626 * 627 * (set|clear|inc|drop)_nlink 628 * inode_(inc|dec)_link_count 629 */ 630 union { 631 const unsigned int i_nlink; 632 unsigned int __i_nlink; 633 }; 634 dev_t i_rdev; 635 loff_t i_size; 636 struct timespec i_atime; 637 struct timespec i_mtime; 638 struct timespec i_ctime; 639 spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ 640 unsigned short i_bytes; 641 unsigned int i_blkbits; 642 blkcnt_t i_blocks; 643 644 #ifdef __NEED_I_SIZE_ORDERED 645 seqcount_t i_size_seqcount; 646 #endif 647 648 /* Misc */ 649 unsigned long i_state; 650 struct rw_semaphore i_rwsem; 651 652 unsigned long dirtied_when; /* jiffies of first dirtying */ 653 unsigned long dirtied_time_when; 654 655 struct hlist_node i_hash; 656 struct list_head i_io_list; /* backing dev IO list */ 657 #ifdef CONFIG_CGROUP_WRITEBACK 658 struct bdi_writeback *i_wb; /* the associated cgroup wb */ 659 660 /* foreign inode detection, see wbc_detach_inode() */ 661 int i_wb_frn_winner; 662 u16 i_wb_frn_avg_time; 663 u16 i_wb_frn_history; 664 #endif 665 struct list_head i_lru; /* inode LRU list */ 666 struct list_head i_sb_list; 667 struct list_head i_wb_list; /* backing dev writeback list */ 668 union { 669 struct hlist_head i_dentry; 670 struct rcu_head i_rcu; 671 }; 672 u64 i_version; 673 atomic_t i_count; 674 atomic_t i_dio_count; 675 atomic_t i_writecount; 676 #ifdef CONFIG_IMA 677 atomic_t i_readcount; /* struct files open RO */ 678 #endif 679 const struct file_operations *i_fop; /* former ->i_op->default_file_ops */ 680 struct file_lock_context *i_flctx; 681 struct address_space i_data; 682 struct list_head i_devices; 683 union { 684 struct pipe_inode_info *i_pipe; 685 struct block_device *i_bdev; 686 struct cdev *i_cdev; 687 char *i_link; 688 unsigned i_dir_seq; 689 }; 690 691 __u32 i_generation; 692 693 #ifdef CONFIG_FSNOTIFY 694 __u32 i_fsnotify_mask; /* all events this inode cares about */ 695 struct hlist_head i_fsnotify_marks; 696 #endif 697 698 #if IS_ENABLED(CONFIG_FS_ENCRYPTION) 699 struct fscrypt_info *i_crypt_info; 700 #endif 701 702 void *i_private; /* fs or device private pointer */ 703 };
這里面與本文相關的成員主要有:
struct inode
--602-->i_mode表示訪問權限控制
--604-->UID
--605-->GID
--606-->i_flags文件系統標志
--630-->硬鏈接數計數
--635-->i_size以字節為單位的文件大小
--636-->最后access時間
--637-->最后modify時間
--638-->最后change時間
--669-->i_dentry; //目錄項鏈表
--673-->i_count引用計數,當引用計數變為0時,會釋放inode實例
--675-->i_writecount寫者計數
--679-->創建設備文件的時候i_fops填充的是def_chr_fops,blk_blk_fops,def_fifo_fops,bad_sock_fops之一,參見創建過程中調用的init_special_inode()
--683-->特殊文件類型的union,pipe,cdev,blk.link etc,i_cdev表示這個inode屬于一個字符設備文件,本文中創建設備文件的時候會把與之相關的設備號的驅動對象cdev拿來填充
--702-->inode的私有數據
上面的幾個成員只有struct def_chr_fops?值得一追,后面有大用:
//fs/char_dev.c429 const struct file_operations def_chr_fops = { 430 .open = chrdev_open,431 .llseek = noop_llseek,432 };
struct file
Linux內核會為每一個進程維護一個文件描述符表,這個表其實就是struct file[]的索引。open()的過程其實就是根據傳入的路徑填充好一個file結構并將其賦值到數組中并返回其索引。下面是file的主要內容
//include/linux/fs.h 877 struct file { 878 union { 879 struct llist_node fu_llist; 880 struct rcu_head fu_rcuhead; 881 } f_u; 882 struct path f_path; 883 struct inode *f_inode; /* cached value */ 884 const struct file_operations *f_op; 885 886 /* 887 * Protects f_ep_links, f_flags. 888 * Must not be taken from IRQ context. 889 */ 890 spinlock_t f_lock; 891 atomic_long_t f_count; 892 unsigned int f_flags; 893 fmode_t f_mode; 894 struct mutex f_pos_lock; 895 loff_t f_pos; 896 struct fown_struct f_owner; 897 const struct cred *f_cred; 898 struct file_ra_state f_ra;f 904 /* needed for tty driver, and maybe others */ 905 void *private_data; 912 struct address_space *f_mapping; 913 } __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
struct file
--882-->f_path里存儲的是open傳入的路徑,VFS就是根據這個路徑逐層找到相應的inode
--883-->f_inode里存儲的是找到的inode
--884-->f_op里存儲的就是驅動提供的file_operations對象,這個對象在open的時候被填充,具體地,應用層的open通過層層搜索會調用inode.i_fops->open,即chrdev_open()
--891-->f_count的作用是記錄對文件對象的引用計數,也即當前有多少個使用CLONE_FILES標志克隆的進程在使用該文件。典型的應用是在POSIX線程中。就像在內核中普通的引用計數模塊一樣,最后一個進程調用put_files_struct()來釋放文件描述符。
--892-->f_flags當打開文件時指定的標志,對應系統調用open的int flags,比如驅動程序為了支持非阻塞型操作需要檢查這個標志是否有O_NONBLOCK。
--893-->f_mode;對文件的讀寫模式,對應系統調用open的mod_t mode參數,比如O_RDWR。如果驅動程序需要這個值,可以直接讀取這個字段。
--905-->private_data表示file結構的私有數據
我在Linux設備管理(二)_從cdev_add說起一文中已經分析過chrdev_open(),這里僅作概述。
//fs/chr_dev.c348 /*349 * Called every time a character special file is opened350 */351 static int chrdev_open(struct inode *inode, struct file *filp)352 { /* 搜索cdev */ ...390 replace_fops(filp, fops);391 if (filp->f_op->open) {392 ret = filp->f_op->open(inode, filp);393 if (ret)394 goto out_cdev_put;395 } ...402 }
可以看出,這個函數有三個任務(劃重點!!!):
chrdev_open()
--352-389-->利用container_of等根據inode中的成員找到相應的cdev
--390-->用cdev.fops替換filp->f_op,即填充了一個空的struct file的f_op成員。
--392-->回調替換之后的filp->f_op->open,由于替換,這個其實就是cdev.fops
至此,我們知道了我們寫的驅動中的open()在何時會被回調,這樣我們就可以實現很多有意思的功能,比如,
我們可以在open中通過inode->cdev來識別具體的設備,并將其私有數據隱藏到file結構的private_data中,進而識別同一個驅動操作一類設備;
我們也可以在回調cdev.fops->open()階段重新填充file結構的fop,進而實現同一個驅動操作不同的設備,這種思想就是內核驅動中常用的分層!
最后總結一下這些結構之間的關系:
?
評論
查看更多