1.引入
Linux內核的整體架構本就非常龐大,其包含的組件也非常多。而我們怎樣把需要的部分都包含在內核中呢?
一種方法是把所有需要的功能都編譯到Linux內核中。這會導致兩個問題,一是生成的內核會很大,二是如果我們要在現有的內核中新增或刪除功能,將不得不重新編譯內核。有沒有另一種機制可使得編譯出的內核本身并不需要包含所有功能,而在這些功能需要被使用的時候,其對應的代碼被動態地加載到內核中呢?
Linux 提供了這樣的機制,這種機制被稱為模塊(Module)。模塊具有這樣的特點,模塊本身不被編譯人內核映像,從而控制了內核的大小。模塊一旦被加載,它就和內核中的其他部分完全一樣。
Linux驅動開發就是編寫上面提到的模塊。
應用程序和 VFS 之間的接口是系統調用,而 VFS 與文件系統以及設備文件之間的接口是 file_operations
結構體成員函數,這個結構體包含對文件進行打開、關閉、讀寫、控制的系列成員函數,關系如圖5.2所示。
由于字符設備的上層沒有類似于磁盤的ext2等文件系統,所以字符設備的file_operations成員函數就直接由設備驅動提供了file_operations正是字符設備驅動的核心。
塊設備有兩種訪問方法:
一種方法是不通過文件系統直接訪問裸設備,在 Linux內核實現了統一的 def blk_fops
這一file_operations
,它的源代碼位于fs/block_dev.c,所以當我們運行類似于“dd if=/dev/sdbl of-sdbl.img”
的命令把整個 /dev/sdb1 裸分區復制到 sdbl.img的時候,內核走的是def blk_fops
這個file_operations
;
另外一種方法是通過文件系統來訪問塊設備,file_operations
的實現則位于文件系統內,文件系統會把針對文件的讀寫轉換為針對塊設備原始扇區的讀寫。ext2、fat、Btrfs等文件系統中會實現針對VFS的file operations成員函數,設備驅動層將看不到file_operations
的存在。
!
2.struct file、struct inode
在設備驅動程序的設計中,一般面言,會關心file
和inode
這兩個結構體
file結構體代表一個打開的文件,系統中每個打開的文件在內核空間都有一個關聯的struct file。它由內核在打開文件時創建,并傳遞給在文件上進行操作的任何函數。在文件的在內核和驅動源代碼中,struct file的指針通常所有實例都關閉后,內核釋放這個數據結構。被命名為file或filp(即 file pointer)。
struct file {union {struct llist_node fu_llist;struct rcu_head fu_rcuhead;} f_u;struct path f_path;struct inode *f_inode; /* cached value */const struct file_operations *f_op;/** Protects f_ep_links, f_flags.* Must not be taken from IRQ context.*/spinlock_t f_lock;atomic_long_t f_count;unsigned int f_flags;fmode_t f_mode;struct mutex f_pos_lock;loff_t f_pos;struct fown_struct f_owner;const struct cred *f_cred;struct file_ra_state f_ra;u64 f_version;
#ifdef CONFIG_SECURITYvoid *f_security;
#endif/* needed for tty driver, and maybe others */void *private_data;#ifdef CONFIG_EPOLL/* Used by fs/eventpoll.c to link all the hooks to this file */struct list_head f_ep_links;struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */struct address_space *f_mapping;
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
文件讀/寫模式mode、標志flags 都是設備驅動關心的內容,而私有數據指針 private_data在設備驅動中被廣泛應用,大多被指向設備驅動自定義以用于描述設備的結構體。
struct inode
包含文件訪問權限、屬主、組、大小、生成時間、訪問時間、最后修改時間等信息。它是Linux管理文件系統的最基本單位,也是文件系統連接任何子目錄、文件的橋梁。
struct inode {umode_t i_mode;unsigned short i_opflags;kuid_t i_uid;kgid_t i_gid;unsigned int i_flags;#ifdef CONFIG_FS_POSIX_ACLstruct posix_acl *i_acl;struct posix_acl *i_default_acl;
#endifconst struct inode_operations *i_op;struct super_block *i_sb;struct address_space *i_mapping;#ifdef CONFIG_SECURITYvoid *i_security;
#endif/* Stat data, not accessed from path walking */unsigned long i_ino;/** Filesystems may only read i_nlink directly. They shall use the* following functions for modification:** (set|clear|inc|drop)_nlink* inode_(inc|dec)_link_count*/union {const unsigned int i_nlink;unsigned int __i_nlink;};dev_t i_rdev;loff_t i_size;struct timespec i_atime;struct timespec i_mtime;struct timespec i_ctime;spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */unsigned short i_bytes;unsigned int i_blkbits;blkcnt_t i_blocks;#ifdef __NEED_I_SIZE_ORDEREDseqcount_t i_size_seqcount;
#endif/* Misc */unsigned long i_state;struct rw_semaphore i_rwsem;unsigned long dirtied_when; /* jiffies of first dirtying */unsigned long dirtied_time_when;struct hlist_node i_hash;struct list_head i_io_list; /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACKstruct bdi_writeback *i_wb; /* the associated cgroup wb *//* foreign inode detection, see wbc_detach_inode() */int i_wb_frn_winner;u16 i_wb_frn_avg_time;u16 i_wb_frn_history;
#endifstruct list_head i_lru; /* inode LRU list */struct list_head i_sb_list;struct list_head i_wb_list; /* backing dev writeback list */union {struct hlist_head i_dentry;struct rcu_head i_rcu;};u64 i_version;atomic_t i_count;atomic_t i_dio_count;atomic_t i_writecount;
#ifdef CONFIG_IMAatomic_t i_readcount; /* struct files open RO */
#endifconst struct file_operations *i_fop; /* former ->i_op->default_file_ops */struct file_lock_context *i_flctx;struct address_space i_data;struct list_head i_devices;union {struct pipe_inode_info *i_pipe;struct block_device *i_bdev;struct cdev *i_cdev;char *i_link;unsigned i_dir_seq;};__u32 i_generation;#ifdef CONFIG_FSNOTIFY__u32 i_fsnotify_mask; /* all events this inode cares about */struct hlist_head i_fsnotify_marks;
#endif#if IS_ENABLED(CONFIG_FS_ENCRYPTION)struct fscrypt_info *i_crypt_info;
#endifvoid *i_private; /* fs or device private pointer */
};
對于表示設備文件的inode結構,struct cdev *i_cdev;
字段包含設備編號。Linux內核設備編號分為主設備編號和次設備編號,前者為devt的高12位.后者為 devt的低 20位。
3.udev
**devfs(設備文件系統)**是由Linux2.4內核引入的,引入時被許多工程師給予了高度評價它的出現使得設備驅動程序能自主地管理自己的設備文件。具體來說,devs具有如下優點
- 可以通過程序在設備初始化時在/dev目錄下創建設備文件,卸載設備時將它刪除
- 設備驅動程序可以指定設備名、所有者和權限位,用戶空間程序仍可以修改所有者和權限位。
- 不再需要為設備驅動程序分配主設備號以及處理次設備號,在程序中可以直接給register chrdev()傳遞0主設備號以獲得可用的主設備號,并在devfsregister()中指定次設備號。
盡管 devfs有這樣和那樣的優點,但是,在Linux2.6內核中,devfs被認為是過時的方法,并最終被拋棄了,udev取代了它。
在嵌人式系統中,也可以用 udev 的輕量級版本 mdev,mdev 集成于 busybox 中。
Linux設計中強調的一個基本觀點是機制和策略的分離。機制是做某樣事情的固定步驟、方法,而策略就是每一個步驟所采取的不同方式。機制是相對固定的,而每個步驟采用的策略是不固定的。機制是穩定的,而策略則是靈活的,因此,在Linux內核中,不應該實現策略。
熱插拔:
udev完全在用戶態工作利用設備加人或移除時內核所發送的熱插拔事件(Hotplug),在熱插拔時,設備的詳細信息會由內核通過netlink
套接字發送出來,發出(Event
)來工作的事情叫 uevent
。udev的設備命名策略、權限控制和事件處理都是在用戶態下完成的,它利用從內核收到的信息來進行創建設備文件節點等工作。
冷插拔:
udev就是采用這種方式接收netlink消息,并根據它的內容和用戶設置給udev的規則做匹配來進行工作的。這里有一個問題,就是冷插拔的設備怎么辦?冷插拔的設備在開機時就存在,在udev啟動前已經被插入了。對于冷插拔的設備,Linux內核提供了sysfs下面一個uevent節點,可以往該節點寫一個“add”,導致內核重新發送netlink,之后udev就可以收到冷插拔的netlink 消息了。
udev自動加載驅動模塊的原理
#設備被發現時發送事件:當硬件設備被插入系統時,內核會檢測到該設備,并通過kobject_uevent函數向用戶空間發送一個uevent事件。這個事件包含了設備的相關信息,如設備的Vendor ID、Product ID等。
#udev監聽事件并處理:udev守護進程在用戶空間運行,監聽這些uevent事件。udev根據事件中的設備信息,查找/lib/modules/uname-r/modules.alias文件,確定需要加載的驅動模塊。
#加載驅動模塊:udev通過調用modprobe命令加載對應的驅動模塊。modprobe會根據模塊的依賴關系,自動加載所有必要的依賴模塊。#注意:/lib/modules/uname-r/modules.alias文件的內容是在內核模塊編譯和安裝時由make modules_install命令生成的。它包含了模塊的別名映射,用于幫助udev或其他工具根據設備的硬件信息找到并加載正確的驅動模塊。模塊編譯進內核時不會出現在modules.alias文件中,只有作為可加載模塊編譯時才會被包含在內。insmod命令用于手動加載模塊,但不會修改modules.alias文件。
**udev的設計者認為inux應該在設備被發現的時候加載驅動模塊,而不是當它被訪問的時候。**udev的設計者認為devs所提供的打開/dev節點時自動加載驅動的功能對一個配置正確的計算機來說是多余的,系統中所有的設備都應該產生熱插拔事件并加載恰當的驅動,而udev能注意到這點并且為它創建對應的設備節點。
4.sysfs文件系統與Linux設備模型
Linux 2.6以后的內核引人了sysfs 文件系統,sys被看成是與 proc、devfs和 devpty 同類別的文件系統,該文件系統是一個虛擬的文件系統,它可以產生一個包括所有系統硬件的層級視圖,與提供進程和狀態信息的proc文件系統十分類似。
sysfs把連接在系統上的設備和總線組織成為一個分級的文件,它們可以由用戶空間存取,向用戶空間導出內核數據結構以及它們的屬性。sysfs的一個目的就是展示設備驅動模型中各組件的層次關系,其頂級目錄包括 block、bus、dev、devices、class、fs、kernel、power和 frmware 等。
block目錄包含所有的塊設備;devices目錄包含系統所有的設備,并根據設備掛接的總線類型組織成層次結構;bus日錄包含系統中所有的總線類型;class目錄包含系統中的設備類型(如網卡設備、聲卡設備、輸人設備等)。
在 /sys/bus的 pci等子目錄下,又會再分出 drivers和devices目錄,而devices 目錄中的文件是對/sys/devices目錄中文件的符號鏈接。同樣地,/sys/class目錄下也包含許多對/sys/devices下文件的鏈接。如圖5.3所示,Linux設備模型與設備、驅動、總線和類的現實狀況是直接對應的,也正符合Linux2.6以后內核的設備模型。
大多數情況下,Linux2.6以后的內核中的設備驅動核心層代碼作為“幕后大佬”可處理好這些關系,內核中的總線和其他內核子系統會完成與設備模型的交互,這使得驅動工程師在編寫底層驅動的時候幾乎不需要關心設備模型,只需要按照每個框架的要求,"填鴨式地填充xxx driver里面的各種回調函數,xxx是總線的名字,在 Linux 內核中,分別使用 bus type
、device driver
和 device
來描述總線、驅動和設備,這3個結構體定義于include/linux/device.h
頭文件中,其定義如代碼清單5.7所示。
struct bus_type {const char *name;const char *dev_name;struct device *dev_root;struct device_attribute *dev_attrs; /* use dev_groups instead */const struct attribute_group **bus_groups;const struct attribute_group **dev_groups;const struct attribute_group **drv_groups;int (*match)(struct device *dev, struct device_driver *drv);int (*uevent)(struct device *dev, struct kobj_uevent_env *env);int (*probe)(struct device *dev);int (*remove)(struct device *dev);void (*shutdown)(struct device *dev);int (*online)(struct device *dev);int (*offline)(struct device *dev);int (*suspend)(struct device *dev, pm_message_t state);int (*resume)(struct device *dev);const struct dev_pm_ops *pm;const struct iommu_ops *iommu_ops;struct subsys_private *p;struct lock_class_key lock_key;
};
struct device_driver {const char *name; // 驅動程序的名稱struct bus_type *bus; // 驅動程序所屬的總線類型(如 platform_bus_type、i2c_bus_type 等)// 內核通過總線類型將驅動程序與設備匹配struct module *owner; // 指向擁有該驅動程序的模塊(通常使用 THIS_MODULE)const char *mod_name; // 用于內置模塊的名稱(通常用于模塊卸載時)bool suppress_bind_attrs; /* disables bind/unbind via sysfs */// 如果為 true,則禁用通過 sysfs 進行驅動程序的綁定和解綁操作enum probe_type probe_type; // 設備探測類型,用于控制探測行為(如同步探測或異步探測)const struct of_device_id *of_match_table; // 設備樹匹配表,用于支持設備樹(Device Tree)的設備匹配const struct acpi_device_id *acpi_match_table; // ACPI 匹配表,用于支持 ACPI 設備的匹配int (*probe) (struct device *dev); // 設備探測函數,當驅動程序與設備匹配成功時調用int (*remove) (struct device *dev); // 設備移除函數,當設備從系統中移除時調用void (*shutdown) (struct device *dev); // 設備關閉函數,當系統關閉時調用int (*suspend) (struct device *dev, pm_message_t state);// 設備掛起函數,當設備進入休眠狀態時調用int (*resume) (struct device *dev); // 設備恢復函數,當設備從休眠狀態恢復時調用const struct attribute_group **groups; // 驅動程序的屬性組const struct dev_pm_ops *pm;// 電源管理操作集,用于定義設備的電源管理操作struct driver_private *p; // 驅動程序私有數據
};
struct device {struct device *parent;struct device_private *p;struct kobject kobj;const char *init_name; /* initial name of the device */const struct device_type *type;struct mutex mutex; /* mutex to synchronize calls to* its driver.*/struct bus_type *bus; /* type of bus device is on */struct device_driver *driver; /* which driver has allocated thisdevice */void *platform_data; /* Platform specific data, devicecore doesn't touch it */void *driver_data; /* Driver data, set and get withdev_set/get_drvdata */struct dev_links_info links;struct dev_pm_info power;struct dev_pm_domain *pm_domain;#ifdef CONFIG_GENERIC_MSI_IRQ_DOMAINstruct irq_domain *msi_domain;
#endif
#ifdef CONFIG_PINCTRLstruct dev_pin_info *pins;
#endif
#ifdef CONFIG_GENERIC_MSI_IRQstruct list_head msi_list;
#endif#ifdef CONFIG_NUMAint numa_node; /* NUMA node this device is close to */
#endifu64 *dma_mask; /* dma mask (if dma'able device) */u64 coherent_dma_mask;/* Like dma_mask, but foralloc_coherent mappings asnot all hardware supports64 bit addresses for consistentallocations such descriptors. */unsigned long dma_pfn_offset;struct device_dma_parameters *dma_parms;struct list_head dma_pools; /* dma pools (if dma'ble) */struct dma_coherent_mem *dma_mem; /* internal for coherent memoverride */
#ifdef CONFIG_DMA_CMAstruct cma *cma_area; /* contiguous memory area for dmaallocations */
#endif/* arch specific additions */struct dev_archdata archdata;struct device_node *of_node; /* associated device tree node */struct fwnode_handle *fwnode; /* firmware device node */dev_t devt; /* dev_t, creates the sysfs "dev" */u32 id; /* device instance */spinlock_t devres_lock;struct list_head devres_head;struct klist_node knode_class;struct class *class;const struct attribute_group **groups; /* optional groups */void (*release)(struct device *dev);struct iommu_group *iommu_group;struct iommu_fwspec *iommu_fwspec;bool offline_disabled:1;bool offline:1;
};
注點:總線、驅動和設備最終都會落實為sysfs中的1個目錄,因為進一步追蹤代碼會發現,它們實際上都可以認為是 kobject
的派生類,kobject
可看作是所有總線、設備和驅動的抽象基類,1個kobject
對應sys中的1個目錄.
總線設備和驅動中的各個attribute 直接落實為sysfs中的一個文件,attribute 會伴隨著show()
和store()
這兩個函數。分別用于讀寫該attribute 對應的sysfs文件。
下面給出了 attribute、bus attribute、driver attribute 和 device attribute 這幾個結構體的定義。
struct attribute {const char *name;umode_t mode;
#ifdef CONFIG_DEBUG_LOCK_ALLOCbool ignore_lockdep:1;struct lock_class_key *key;struct lock_class_key skey;
#endif
};struct bus_attribute {struct attribute attr;ssize_t (*show)(struct bus_type *bus, char *buf);ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count);
};struct device_attribute {struct attribute attr;ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);ssize_t (*store)(struct device *dev, struct device_attribute *attr,const char *buf, size_t count);
};struct driver_attribute {struct attribute attr;ssize_t (*show)(struct device_driver *driver, char *buf);ssize_t (*store)(struct device_driver *driver, const char *buf,size_t count);
};
事實上sysfs中的目錄來源于 bus type、device_driver、device,而目錄中的文件則來源attribute.
Linux內核中也定義了一些快捷方式以方便attribute的創建工作:
#define BUS_ATTR(_name, _mode, _show, _store) \struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define BUS_ATTR_RW(_name) \struct bus_attribute bus_attr_##_name = __ATTR_RW(_name)
#define BUS_ATTR_RO(_name) \struct bus_attribute bus_attr_##_name = __ATTR_RO(_name)#define DRIVER_ATTR(_name, _mode, _show, _store) \struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DRIVER_ATTR_RW(_name) \struct driver_attribute driver_attr_##_name = __ATTR_RW(_name)
#define DRIVER_ATTR_RO(_name) \struct driver_attribute driver_attr_##_name = __ATTR_RO(_name)
#define DRIVER_ATTR_WO(_name) \struct driver_attribute driver_attr_##_name = __ATTR_WO(_name)#define DEVICE_ATTR(_name, _mode, _show, _store) \struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store)
#define DEVICE_ATTR_RW(_name) \struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
#define DEVICE_ATTR_RO(_name) \struct device_attribute dev_attr_##_name = __ATTR_RO(_name)
#define DEVICE_ATTR_WO(_name) \struct device_attribute dev_attr_##_name = __ATTR_WO(_name)
#define DEVICE_ULONG_ATTR(_name, _mode, _var) \struct dev_ext_attribute dev_attr_##_name = \{ __ATTR(_name, _mode, device_show_ulong, device_store_ulong), &(_var) }
#define DEVICE_INT_ATTR(_name, _mode, _var) \struct dev_ext_attribute dev_attr_##_name = \{ __ATTR(_name, _mode, device_show_int, device_store_int), &(_var) }
#define DEVICE_BOOL_ATTR(_name, _mode, _var) \struct dev_ext_attribute dev_attr_##_name = \{ __ATTR(_name, _mode, device_show_bool, device_store_bool), &(_var) }
#define DEVICE_ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) \struct device_attribute dev_attr_##_name = \__ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store)
比如,我們在 drivers/base/bus.c文件中可以找到這樣的代碼
static BUS_ATTR(uevent, S_IWUSR, NULL, bus_uevent_store);static BUS_ATTR(drivers_probe, S_IWUSR, NULL, store_drivers_probe);
static BUS_ATTR(drivers_autoprobe, S_IWUSR | S_IRUGO,show_drivers_autoprobe, store_drivers_autoprobe);
而在 /sys/bus/platform 等里面就可以找到對應的文件
[root@100ask:/sys/bus/platform]# ls
devices drivers drivers_autoprobe drivers_probe uevent