首頁  >  文章  >  系統教程  >  Linux系統中的裝置檔案:inode,file和file_operations

Linux系統中的裝置檔案:inode,file和file_operations

WBOY
WBOY轉載
2024-02-13 10:20:38622瀏覽

設備檔案是Linux系統中一種特殊的文件,它用來表示設備的接口,使得用戶空間的程式可以透過檔案操作來存取設備。設備檔案的實作涉及到三個重要的結構體:inode,file和file_operations。 inode結構體用來儲存裝置檔案的元數據,如裝置號,權限,大小等。 file結構體用來儲存裝置檔案的狀態訊息,如目前位置,開啟模式,私有資料等。 file_operations結構體用來儲存裝置檔案的操作函數,如open,read,write,close等。在本文中,我們將介紹這三個結構體的定義和作用,並舉例說明它們的使用方法和注意事項。

Linux系統中的裝置檔案:inode,file和file_operations

#驅動程式就是向下控制硬件,向上提供接口,這裡的向上提供的接口最終對應到應用層有三種方式:設備文件,/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.c
429 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.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,进而实现同一个驱动操作不同的设备,这种思想就是内核驱动中常用的分层!
最后总结一下这些结构之间的关系:

Linux系統中的裝置檔案:inode,file和file_operations

透過本文,我們了解了裝置檔案的三個重要的結構體:inode,file和file_operations。它們可以用來實現對設備文件的管理和操作。我們應該根據實際需求選擇合適的結構體,並遵循一些基本原則,如使用正確的設備號,使用合理的權限,使用有效的操作函數等。設備文件的三個結構體是Linux系統中最基本的概念之一,它們可以實現對設備的抽象和封裝,也可以提升系統的統一性和靈活性。希望本文能對你有所幫助與啟發。

以上是Linux系統中的裝置檔案:inode,file和file_operations的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:lxlinux.net。如有侵權,請聯絡admin@php.cn刪除