Linux 設備驅動之字符設備

參考轉載博客:http://blog.chinaunix.net/uid-26833883-id-4369060.htmlhtml

                        https://www.cnblogs.com/xiaojiang1025/p/6181833.htmlnode

                        http://blog.csdn.net/yueqian_scut/article/details/45938557linux

                       http://www.cnblogs.com/geneil/archive/2011/12/03/2272869.htmlswift

                       http://blog.csdn.net/z781567363r/article/details/23422743數組

                      http://blog.csdn.net/dcx1205/article/details/45877345網絡

                     http://blog.chinaunix.net/uid-21556133-id-3408488.htmlapp

                    https://www.cnblogs.com/chen-farsight/p/6177870.htmlide

                 http://blog.csdn.net/zqixiao_09/article/details/50850004函數

                 http://blog.csdn.net/maopig/article/details/7195048oop

              http://blog.csdn.net/yueqian_scut/article/details/46771595

             http://www.linuxidc.com/Linux/2017-02/140227.htm

     驅動程序就是向下控制硬件,向上提供接口,這裏的向上提供的接口最終對應到應用層有三種方式:設備文件,/proc,/sys,其中最經常使用的就是使用設備文件,而Linux設備中用的最多的就是字符設備,本文就以字符設備爲例來分析建立並打開一個字符設備的文件內部機制。每個字符設備或塊設備都在/dev目錄下對應一個設備文件。linux用戶程序經過設備文件(或稱設備節點)來使用驅動程序操做字符設備和塊設備                   

 一)字符設備概述

一、linux系統將設備分爲3類:字符設備、塊設備、網絡設備。使用驅動程序:

一、字符設備:是指只能一個字節一個字節讀寫的設備,不能隨機讀取設備內存中的某一數據,讀取數據須要按照前後數據。字符設備是面向流的設備,常見的字符設備有鼠標、鍵盤、串口、控制檯和LED設備等。
二、塊設備:是指能夠從設備的任意位置讀取必定長度數據的設備。塊設備包括硬盤、磁盤、U盤和SD卡等。

2、字符設備流程(從上層應用到底層驅動)

(1)建立設備文件

插入的設備模塊,咱們就可使用cat /proc/devices命令查看當前系統註冊的設備,可是咱們尚未建立相應的設備文件,用戶也就不能經過文件訪問這個設備。設備文件的inode應該是包含了這個設備的設備號,操做方法集指針等信息,這樣咱們就能夠經過設備文件找到相應的inode進而訪問設備。建立設備文件的方法有兩種,手動建立自動建立手動建立設備文件就是使用mknod /dev/xxx 設備類型 主設備號 次設備號的命令建立,因此首先須要使用cat /proc/devices查看設備的主設備號並經過源碼找到設備的次設備號,須要注意的是,理論上設備文件能夠放置在任何文件加夾,可是放到"/dev"才符合Linux的設備管理機制,這裏面的devtmpfs是專門設計用來管理設備文件的文件系統。設備文件建立好以後就會和建立時指定的設備綁定,即便設備已經被卸載了,如要刪除設備文件,只須要像刪除普通文件同樣rm便可。理論上模塊名(lsmod),設備名(/proc/devices),設備文件名(/dev)並無什麼關係,徹底能夠不同,可是原則上仍是建議將三者進行統一,便於管理。

除了使用蹩腳的手動建立設備節點的方式,咱們還能夠在設備源碼中使用相應的措施使設備一旦被加載就自動建立設備文件,自動建立設備文件須要咱們在編譯內核的時候或製做根文件系統的時候就好相應的配置:

Device Drivers ---> Generic Driver Options ---> [*]Maintain a devtmpfs filesystem to mount at /dev [*] Automount devtmpfs at /dev,after the kernel mounted the rootfs

OR
製做根文件系統的啓動腳本寫入

mount -t sysfs none sysfs /sys mdev -s //udev也行

有了這些準備,只須要導出相應的設備信息到"/sys"就能夠按照咱們的要求自動建立設備文件。內核給咱們提供了相關的API

class_create(owner,name);
struct device *device_create_vargs(struct class *cls, struct device *parent,dev_t devt, void *drvdata,const char *fmt, va_list vargs); void class_destroy(struct class *cls); void device_destroy(struct class *cls, dev_t devt);

有了這幾個函數,咱們就能夠在設備的xxx_init()xxx_exit()中分別填寫如下的代碼就能夠實現自動的建立刪除設備文件

/* 在/sys中導出設備類信息 */ cls = class_create(THIS_MODULE,DEV_NAME); /* 在cls指向的類中建立一組(個)設備文件 */ for(i= minor;i<(minor+cnt);i++){ devp = device_create(cls,NULL,MKDEV(major,i),NULL,"%s%d",DEV_NAME,i); } 
/* 在cls指向的類中刪除一組(個)設備文件 */ for(i= minor;i<(minor+cnt);i++){ device_destroy(cls,MKDEV(major,i)); } /* 在/sys中刪除設備類信息 */ class_destroy(cls); //必定要先卸載device再卸載class

 

 

 

 

 

 

 

 

 

 

 

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,進而實現同一個驅動操做不一樣的設備,這種思想就是內核驅動中經常使用的分層!最後總結一下這些結構之間的關係:

相關文章
相關標籤/搜索