設備檔案是Linux系統中一種特殊的文件,它用來表示設備的接口,使得用戶空間的程式可以透過檔案操作來存取設備。設備檔案的實作涉及到三個重要的結構體:inode,file和file_operations。 inode結構體用來儲存裝置檔案的元數據,如裝置號,權限,大小等。 file結構體用來儲存裝置檔案的狀態訊息,如目前位置,開啟模式,私有資料等。 file_operations結構體用來儲存裝置檔案的操作函數,如open,read,write,close等。在本文中,我們將介紹這三個結構體的定義和作用,並舉例說明它們的使用方法和注意事項。
#驅動程式就是向下控制硬件,向上提供接口,這裡的向上提供的接口最終對應到應用層有三種方式:設備文件,/proc,/sys,其中最常用的就是使用設備文件,而Linux設備中用的最多的就是字元設備,本文就以字元設備為例來分析創建並開啟一個字元設備的文件內部機制。
#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.c 429 const struct file_operations def_chr_fops = { 430 .open = chrdev_open, 431 .llseek = noop_llseek, 432 };
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.c 348 /* 349 * Called every time a character special file is opened 350 */ 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,进而实现同一个驱动操作不同的设备,这种思想就是内核驱动中常用的分层!
最后总结一下这些结构之间的关系:
“
”
透過本文,我們了解了裝置檔案的三個重要的結構體:inode,file和file_operations。它們可以用來實現對設備文件的管理和操作。我們應該根據實際需求選擇合適的結構體,並遵循一些基本原則,如使用正確的設備號,使用合理的權限,使用有效的操作函數等。設備文件的三個結構體是Linux系統中最基本的概念之一,它們可以實現對設備的抽象和封裝,也可以提升系統的統一性和靈活性。希望本文能對你有所幫助與啟發。
以上是Linux系統中的裝置檔案:inode,file和file_operations的詳細內容。更多資訊請關注PHP中文網其他相關文章!