色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

一文帶你掌握Linux字符設備架構

璟琰乀 ? 來源:一口Linux ? 作者:一口Linux ? 2020-12-22 16:14 ? 次閱讀

一、Linux設備分類

Linux系統為了管理方便,將設備分成三種基本類型:

字符設備

塊設備

網絡設備

字符設備:

字符(char)設備是個能夠像字節流(類似文件)一樣被訪問的設備,由字符設備驅動程序來實現這種特性。字符設備驅動程序通常至少要實現open、close、read和write的系統調用。

字符終端(/dev/console)和串口(/dev/ttyS0以及類似設備)就是兩個字符設備,它們能很好的說明“流”這種抽象概念。

字符設備可以通過文件節點來訪問,比如/dev/tty1和/dev/lp0等。這些設備文件和普通文件之間的唯一差別在于對普通文件的訪問可以前后移動訪問位置,而大多數字符設備是一個只能順序訪問的數據通道。然而,也存在具有數據區特性的字符設備,訪問它們時可前后移動訪問位置。例如framebuffer就是這樣的一個設備,app可以用mmap或lseek訪問抓取的整個圖像。

在/dev下執行ls -l ,可以看到很多創建好的設備節點:yqqymm.png

字符設備文件(類型為c),設備文件是沒有文件大小的,取而代之的是兩個號碼:主設備號5 +次設備號1 。

塊設備:

和字符設備類似,塊設備也是通過/dev目錄下的文件系統節點來訪問。塊設備(例如磁盤)上能夠容納filesystem。在大多數的Unix系統中,進行I/O操作時塊設備每次只能傳輸一個或多個完整的塊,而每塊包含512字節(或2的更高次冪字節的數據)。

Linux可以讓app像字符設備一樣地讀寫塊設備,允許一次傳遞任意多字節的數據。因此,塊設備和字符設備的區別僅僅在于內核內部管理數據的方式,也就是內核及驅動程序之間的軟件接口,而這些不同對用戶來講是透明的。在內核中,和字符驅動程序相比,塊驅動程序具有完全不同的接口。

塊設備文件(類型為b):

neQJRb.png

網絡設備:

任何網絡事物都需要經過一個網絡接口形成,網絡接口是一個能夠和其他主機交換數據的設備。接口通常是一個硬件設備,但也可能是個純軟件設備,比如回環(loopback)接口。

網絡接口由內核中的網絡子系統驅動,負責發送和接收數據包。許多網絡連接(尤其是使用TCP協議的連接)是面向流的,但網絡設備卻圍繞數據包的傳送和接收而設計。網絡驅動程序不需要知道各個連接的相關信息,它只要處理數據包即可。

由于不是面向流的設備,因此將網絡接口映射到filesystem中的節點(比如/dev/tty1)比較困難。

Unix訪問網絡接口的方法仍然是給它們分配一個唯一的名字(比如eth0),但這個名字在filesystem中不存在對應的節點。內核和網絡設備驅動程序間的通信,完全不同于內核和字符以及塊驅動程序之間的通信,內核調用一套和數據包相關的函數socket,也叫套接字。

查看網絡設備使用命令ifconfig:

6zyqAr.png

二、字符設備架構是如何實現的?

在Linux的世界里面一切皆文件,所有的硬件設備操作到應用層都會被抽象成文件的操作。我們知道如果應用層要訪問硬件設備,它必定要調用到硬件對應的驅動程序。Linux內核中有那么多驅動程序,應用層怎么才能精確的調用到底層的驅動程序呢?

在這里我們字符設備為例,來看一下應用程序是如何和底層驅動程序關聯起來的。必須知道的基礎知識:

1.在Linux文件系統中,每個文件都用一個struct inode結構體來描述,這個結構體里面記錄了這個文件的所有信息,例如:文件類型,訪問權限等。

2.在Linux操作系統中,每個驅動程序在應用層的/dev目錄下都會有一個設備文件和它對應,并且該文件會有對應的主設備號和次設備號。

3.在Linux操作系統中,每個驅動程序都要分配一個主設備號,字符設備的設備號保存在struct cdev結構體中。

struct cdev { struct kobject kobj; struct module *owner; const struct file_operations *ops;//接口函數集合 struct list_head list;//內核鏈表 dev_t dev; //設備號 unsigned int count;//次設備號個數 };

4.在Linux操作系統中,每打開一次文件,Linux操作系統在VFS層都會分配一個struct file結構體來描述打開的這個文件。該結構體用于維護文件打開權限、文件指針偏移值、私有內存地址等信息。

注意:

常常我們認為struct inode描述的是文件的靜態信息,即這些信息很少會改變。而struct file描述的是動態信息,即在對文件的操作的時候,struct file里面的信息經常會發生變化。典型的是struct file結構體里面的f_pos(記錄當前文件的位移量),每次讀寫一個普通文件時f_ops的值都會發生改變。

這幾個結構體關系如下圖所示:

通過上圖我們可以知道,如果想訪問底層設備,就必須打開對應的設備文件。也就是在這個打開的過程中,Linux內核將應用層和對應的驅動程序關聯起來。

1.當open函數打開設備文件時,可以根據設備文件對應的struct inode結構體描述的信息,可以知道接下來要操作的設備類型(字符設備還是塊設備)。還會分配一個struct file結構體。

2.根據struct inode結構體里面記錄的設備號,可以找到對應的驅動程序。這里以字符設備為例。在Linux操作系統中每個字符設備有一個struct cdev結構體。此結構體描述了字符設備所有的信息,其中最重要一項的就是字符設備的操作函數接口。

3.找到struct cdev結構體后,Linux內核就會將struct cdev結構體所在的內存空間首地記錄在struct inode結構體的i_cdev成員中。將struct cdev結構體的中記錄的函數操作接口地址記錄在struct file結構體的f_op成員中。

4.任務完成,VFS層會給應用層返回一個文件描述符(fd)。這個fd是和struct file結構體對應的。接下來上層的應用程序就可以通過fd來找到strut file,然后在由struct file找到操作字符設備的函數接口了。

三、字符驅動相關函數分析

/** * cdev_init() - initialize a cdev structure * @cdev: the structure to initialize * @fops: the file_operations for this device * * Initializes @cdev, remembering @fops, making it ready to add to the * system with cdev_add()。 */void cdev_init(struct cdev *cdev, const struct file_operations *fops)功能: 初始化cdev結構體參數: @cdev cdev結構體地址 @fops 操作字符設備的函數接口地址返回值: 無

/** * register_chrdev_region() - register a range of device numbers * @from: the first in the desired range of device numbers; must include * the major number. * @count: the number of consecutive device numbers required * @name: the name of the device or driver. * * Return value is zero on success, a negative error code on failure. */ int register_chrdev_region(dev_t from, unsigned count, const char *name)功能: 注冊一個范圍()的設備號參數: @from 設備號 @count 注冊的設備個數 @name 設備的名字返回值: 成功返回0,失敗返回錯誤碼(負數)

/** * cdev_add() - add a char device to the system * @p: the cdev structure for the device * @dev: the first device number for which this device is responsible * @count: the number of consecutive minor numbers corresponding to this * device * * cdev_add() adds the device represented by @p to the system, making it * live immediately. A negative error code is returned on failure. */int cdev_add(struct cdev *p, dev_t dev, unsigned count)功能: 添加一個字符設備到操作系統參數: @p cdev結構體地址 @dev 設備號 @count 次設備號個數返回值: 成功返回0,失敗返回錯誤碼(負數)

/** * cdev_del() - remove a cdev from the system * @p: the cdev structure to be removed * * cdev_del() removes @p from the system, possibly freeing the structure * itself. */void cdev_del(struct cdev *p)功能: 從系統中刪除一個字符設備參數: @p cdev結構體地址返回值: 無

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)功能: 注冊或者分配設備號,并注冊fops到cdev結構體, 如果major》0,功能為注冊該主設備號, 如果major=0,功能為動態分配主設備號。參數: @major : 主設備號 @name : 設備名稱,執行 cat /proc/devices顯示的名稱 @fops : 文件系統的接口指針返回值 如果major》0 成功返回0,失敗返回負的錯誤碼 如果major=0 成功返回主設備號,失敗返回負的錯誤碼

該函數實現了對cdev的初始化和注冊的封裝,所以調用該函數之后就不需要自己操作cdev了。

相對的注銷函數為unregister_chrdev

static inline void unregister_chrdev(unsigned int major, const char *name)

四、如何編寫字符設備驅動

aeYnIj.png

參考上圖,編寫字符設備驅動步驟如下:

1. 實現模塊加載和卸載入口函數

module_init (hello_init);module_exit (hello_exit);

2. 申請主設備號

申請主設備號 (內核中用于區分和管理不同字符設備)

register_chrdev_region (devno, number_of_devices, “hello”);

3. 創建設備節點

創建設備節點文件 (為用戶提供一個可操作到文件接口--open()) 創建設備節點有兩種方式:手動方式創建,函數自動創建。手動創建:

mknod /dev/hello c 250 0

自動創建設備節點

除了使用mknod命令手動創建設備節點,還可以利用linux的udev、mdev機制,而我們的ARM開發板上移植的busybox有mdev機制,那么就使用mdev機制來自動創建設備節點。

在etc/init.d/rcS文件里有一句:

echo /sbin/mdev 》 /proc/sys/kernel/hotplug

該名命令就是用來自動創建設備節點。

udev 是一個工作在用戶空間的工具,它能根據系統中硬件設備的狀態動態的更新設備文件,包括設備文件的創建,刪除,權限等。這些文件通常都定義在/dev 目錄下,但也可以在配置文件中指定。udev 必須有內核中的sysfs和tmpfs支持,sysfs 為udev 提供設備入口和uevent 通道,tmpfs 為udev 設備文件提供存放空間。

udev 運行在用戶模式,而非內核中。udev 的初始化腳本在系統啟動時創建設備節點,并且當插入新設備——加入驅動模塊——在sysfs上注冊新的數據后,udev會創新新的設備節點。

注意,udev 是通過對內核產生的設備文件修改,或增加別名的方式來達到自定義設備文件的目的。但是,udev 是用戶模式程序,其不會更改內核行為。也就是說,內核仍然會創建sda,sdb等設備文件,而udev可根據設備的唯一信息來區分不同的設備,并產生新的設備文件(或鏈接)。

例如:

如果驅動模塊可以將自己的設備號作為內核參數導出,在sysfs文件中就有一個叫做uevent文件記錄它的值。

iAJbia.png

由上圖可知,uevent中包含了主設備號和次設備號的值以及設備名字。

在Linux應用層啟動一個udev程序,這個程序的第一次運行的時候,會遍歷/sys目錄,尋找每個子目錄的uevent文件,從這些uevent文件中獲取創建設備節點的信息,然后調用mknod程序在/dev目錄下創建設備節點。結束之后,udev就開始等待內核空間的event。這個設備模型的東西,我們在后面再詳細說。這里大就可以這樣理解,在Linux內核中提供了一些函數接口,通過這些函數接口,我們可在sysfs文件系統中導出我們的設備號的值,導出值之后,內核還會向應用層上報event。此時udev就知道有活可以干了,它收到這個event后,就讀取event對應的信息,接下來就開始創建設備節點啦。

如何創建一個設備類?

第一步 :通過宏class_create() 創建一個class類型的對象;

/* This is a #define to keep the compiler from merging different * instances of the __key variable */#define class_create(owner, name) ({ static struct lock_class_key __key; __class_create(owner, name, &__key); })參數: @owner THIS_MODULE @name 類名字返回值 可以定義一個struct class的指針變量cls接受返回值,然后通過IS_ERR(cls)判斷 是否失敗,如果成功這個宏返回0,失敗返回非9值(可以通過PTR_ERR(cls)來獲得 失敗返回的錯誤碼)

在Linux內核中,把設備進行了分類,同一類設備可以放在同一個目錄下,該函數啟示就是創建了一個類,例如:

qYvE7f.png

第二步:導出我們的設備信息到用戶空間

/** * device_create - creates a device and registers it with sysfs * @class: pointer to the struct class that this device should be registered to * @parent: pointer to the parent struct device of this new device, if any * @devt: the dev_t for the char device to be added * @drvdata: the data to be added to the device for callbacks * @fmt: string for the device‘s name * * This function can be used by char device classes. A struct device * will be created in sysfs, registered to the specified class. * * A “dev” file will be created, showing the dev_t for the device, if * the dev_t is not 0,0. * If a pointer to a parent struct device is passed in, the newly created * struct device will be a child of that device in sysfs. * The pointer to the struct device will be returned from the call. * Any further sysfs files that might be required can be created using this * pointer. * * Returns &struct device pointer on success, or ERR_PTR() on error. * * Note: the struct class passed to this function must have previously * been created with a call to class_create()。 */struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, 。..)

自動創建設備節點使用實例:

static struct class *cls;static struct device *test_device; devno = MKDEV(major,minor); cls = class_create(THIS_MODULE,“helloclass”); if(IS_ERR(cls)) { unregister_chrdev(major,“hello”); return result; } test_device = device_create(cls,NULL,devno,NULL,“hellodevice”); if(IS_ERR(test_device )) { class_destroy(cls); unregister_chrdev(major,“hello”); return result; }

4 實現file_operations

static const struct file_operations fifo_operations = { .owner = THIS_MODULE, .open = dev_fifo_open, .read = dev_fifo_read, .write = dev_fifo_write, .unlocked_ioctl = dev_fifo_unlocked_ioctl,};

open、release對應應用層的open()、close()函數。實現比較簡單,

直接返回0即可。 其中read、write、unloched_ioctrl 函數的實現需要涉及到用戶空間 和內存空間的數據拷貝。

在Linux操作系統中,用戶空間和內核空間是相互獨立的。也就是說內核空間是不能直接訪問用戶空間內存地址,同理用戶空間也不能直接訪問內核空間內存地址。

如果想實現,將用戶空間的數據拷貝到內核空間或將內核空間數據拷貝到用戶空間,就必須借助內核給我們提供的接口來完成。

1. read接口實現

用戶空間--》內核空間

字符設備的write接口定義如下:

ssize_t (*write)(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos);參數: filp:待操作的設備文件file結構體指針 buf:待寫入所讀取數據的用戶空間緩沖區指針 count:待讀取數據字節數 f_pos:待讀取數據文件位置,寫入完成后根據實際寫入字節數重新定位返回: 成功實際寫入的字節數,失敗返回負值

如果該操作為空,將使得write系統調用返回負EINVAL失敗,正常返回實際寫入的字節數。

用戶空間向內核空間拷貝數據需要使用copy_from_user函數,該函數定義在arch/arm/include/asm/uaccess.h中。

static inline int copy_from_user(void *to, const void __user volatile *from,unsigned long n)參數: to:目標地址(內核空間) from:源地址(用戶空間) n:將要拷貝數據的字節數返回: 成功返回0,失敗返回沒有拷貝成功的數據字節數

7ZbaUn.png

還可以使用get_user宏:

int get_user(data, ptr);參數: data:可以是字節、半字、字、雙字類型的內核變量 ptr:用戶空間內存指針返回: 成功返回0,失敗返回非0

2. write接口實現

內核空間--》用戶空間

字符設備的read接口定義如下:

ssize_t (*read)(struct file *filp, char __user *buf, size_t count, lofft *f_pos);參數: filp: 待操作的設備文件file結構體指針 buf: 待寫入所讀取數據的用戶空間緩沖區指針 count:待讀取數據字節數 f_pos:待讀取數據文件位置,讀取完成后根據實際讀取字節數重新定位 __user :是一個空的宏,主要用來顯示的告訴程序員它修飾的指針變量存放的是用戶空間的地址。返回值: 成功實際讀取的字節數,失敗返回負值

注意:如果該操作為空,將使得read系統調用返回負EINVAL失敗,正常返回實際讀取的字節數。

用戶空間從內核空間讀取數據需要使用copy_to_user函數:

static inline int copy_to_user(void __user volatile *to, const void *from,unsigned long n)參數: to:目標地址(用戶空間) from:源地址(內核空間) n:將要拷貝數據的字節數返回: 成功返回0,失敗返回沒有拷貝成功的數據字節數

JzmUVz.png

在這里插入圖片描述

還可以使用put_user宏:

int put_user(data, prt)參數: data:可以是字節、半字、字、雙字類型的內核變量 ptr:用戶空間內存指針返回: 成功返回0, 失敗返回非0

這樣我們就可以實現read、write函數了,實例如下:

ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp){ ssize_t result = 0; if (count 》 127) count = 127; if (copy_to_user (buff, data, count)) { result = -EFAULT; } else { printk (KERN_INFO “wrote %d bytes ”, count); result = count; } return result;}ssize_t hello_write (struct file *filp,const char *buf, size_t count, loff_t *f_pos){ ssize_t ret = 0; //printk (KERN_INFO “Writing %d bytes ”, count); if (count 》 127) return -ENOMEM; if (copy_from_user (data, buf, count)) { ret = -EFAULT; } else { data[count] = ’

主站蜘蛛池模板: 调教椅上的调教SM总裁被调教 | 99久女女精品视频在线观看 | 亚洲视频第二页 | 日本电影免费久久精品 | 日本久久免费大片 | 伊人网站在线 | 高清国产激情视频在线观看 | qvod电影在线观看 | 4438全国免费观看 | 亚洲高清在线mv | 799是什么意思网络用语 | 亚洲最大成人 | 琪琪电影午夜理论片YY6080 | 亚洲不卡视频在线观看 | 性888xxxx入欧美 | 天天爽夜夜爽8888视频精品 | 久久亚洲一级α片 | 美女诱惑性感揉胸 | 受被攻做到腿发颤高h文 | 久久aa毛片免费播放嗯啊 | 国产高清免费观看 | av视频在线免播放观看 | 久久无码人妻AV精品一区 | 日本69色视频在线观看 | 91精品免费久久久久久久久 | 国产又湿又黄又硬又刺激视频 | 芒果视频看片在线观看 | 久久精品视在线观看2 | 欧美午夜理伦三级在线观看 | 亚洲黄色三级视频 | 色橹橹欧美在线观看视频高清 | 好大快用力深一点h视频 | 免费无码一区二区三区蜜桃大 | 出租屋自拍贵在真实15P | 免费人成网站永久 | 免费果冻传媒在线完整观看 | 国产婷婷色综合AV蜜臀AV | 偷尝禁果H1V1幸运的山熊 | 久久久久青草大香线综合精品 | 中文字幕人成人乱码亚洲AV | 亚洲日韩天堂在线中文字幕 |