嵌入式單片機原理第十章_第1頁
嵌入式單片機原理第十章_第2頁
嵌入式單片機原理第十章_第3頁
嵌入式單片機原理第十章_第4頁
嵌入式單片機原理第十章_第5頁
已閱讀5頁,還剩98頁未讀 繼續免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

嵌入式單片機原理第十章第一頁,共103頁。一、嵌入式Linux驅動概述

設備驅動是操作系統內核中最接近硬件設備,是操作系統內核和底層硬件設備之間的接口。也就是說,操作系統內核就是通過調用這些接口函數來完成對底層硬件設備的使用。那么應用程序怎樣使用底層的硬件平臺呢?前面講過,在操作系統和應用程序之間也有很多接口函數,這些接口函數為應用程序使用操作系統內核提供了窗口,我們稱這些接口函數為系統調用。Linux驅動基本原理系統調用是操作系統內核和應用程序之間的接口,設備驅動程序是操作系統內核和機器硬件之間的接口。它們都有一個共同的特點:屏蔽了底層的某個具體服務的實現細節,比如,系統調用屏蔽了操作系統內核某個具體功能的實現細節,設備驅動程序則屏蔽了底層硬件設備的細節。第二頁,共103頁。具體來說,在Linux中設備是被當做文件來進行處理的。上層的應用程序需要操作硬件時,只需要獲得設備的文件描述符,然后通過系統調用open(),read(),write(),ioctl(),close()等來操作設備,這與一般普通的文件操作非常類似。應用程序不關心無需關心硬件的細節。應用程序發出系統調用指令后,會從用戶態轉換為內核態,通過內核將系統調用函數轉換成對物理設備的操作。如圖所示了應用程序使用底層的設備接口示意圖,從中可以看出,設備驅動層起到了承上啟下的作用。一、嵌入式Linux驅動概述Linux驅動基本原理第三頁,共103頁。常見的驅動程序也是作為內核模塊動態加載的,比如聲卡驅動和網卡驅動等,而Linux最基礎的驅動,如CPU、PCI總線、TCP/IP協議、APM(高級電源管理)、VFS等驅動程序則直接編譯在內核文件中。有時也把內核模塊叫做驅動程序,只不過驅動的內容不一定是硬件罷了,比如ext3文件系統的驅動。因此,加載驅動時就是加載內核模塊。

一、嵌入式Linux驅動概述Linux驅動基本原理第四頁,共103頁。作為程序開發者,從上圖可以看出,處于設備驅動層的Linux驅動程序為應用程序提供了訪問硬件設備的編程接口(ApplicationProgrammingInterface,API),它是真個設備驅動的核心內容。驅動程序主要提供以下功能:應用程序通過驅動程序安全有效地訪問硬件;驅動程序作為嵌入式系統的中間層軟件,它隱藏了底層的細節,從而提高了軟件的可移植性和可復用性;驅動程序文件節點可以方便地提供訪問權限控制。一、嵌入式Linux驅動概述Linux驅動功能第五頁,共103頁。從下層驅動開發人員的角度來看,Linux驅動程序就是通過直接操控硬件的軟件,來完成下面的功能:對設備初始化和釋放;直接讀寫硬件寄存器來控制硬件;把數據從內核傳送到硬件和從硬件讀取數據;操作設備緩沖區設備;操作輸入、輸出設備,如鍵盤、打印機等;讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據;檢測和處理設備出現的錯誤。一、嵌入式Linux驅動概述Linux驅動功能第六頁,共103頁。Linux的一個重要特點就是將所有的設備都當做文件進行處理,這一類特殊文件就是設備文件,它們可以使用前面提到的文件、I/O相關函數進行操作,這樣就大大方便了對設備的處理。它通常在“/dev”下面存在一個對應的邏輯設備節點,這個節點以文件的形式存在。二、設備驅動的基礎知識Linux設備管理機制第七頁,共103頁。Linux系統的設備文件分為四類:塊設備文件、字符設備文件、網絡設備文件和雜項設備文件。塊設備文件:通常指一些需要以塊(如512字節)的方式寫入的設備,如IDE硬盤、SCSI硬盤、光驅等;字符型設備文件:通常指可以直接讀寫,沒有緩沖區的設備,如并口、虛擬控制臺等;網絡設備文件:通常是指網絡設備訪問的BSDsocket接口,如網卡等;雜項設備文件:通常指的是比較特殊的驅動程序,如IIC、USB等。二、設備驅動的基礎知識設備分類第八頁,共103頁。設備號是一個數字,它是設備的標志。就如前面所述,一個設備文件(也就是設備節點)可以通過mknod命令來創建,其中指定了主設備號和次設備號。主設備號表明某一類設備,用于標識設備對應的驅動程序,一般對應著確定的驅動程序,主設備號相同的設備使用相同的驅動程序;次設備號一般是用于區分標明不同屬性(例如不同的使用方法,不同的位置,不同的操作等),它標志著某個具體的物理設備。次設備號是一個8位數,用來區分具體設備的實例。因此,同一臺機器上如果有兩個相同的設備,則它們的主設備號相同,但第一個設備的次設備號為0,而第二個設備的次設備號為1。一般,高字節為主設備號和底字節為次設備號。例如,在系統中的塊設備IDE硬盤的主設備號是3,而多個IDE硬盤及其各個分區分別賦予次設備號1、2、3……二、設備驅動的基礎知識設備號第九頁,共103頁。Linux設備的設備號由主、次設備號構成,如果已經知道某設備的主、次設備號,可以利用MKDEV()宏來合成設備號。該宏定義如下(位于include/linux/kdev_t.h中): #defineMKDEV(ma,mi) ((ma)<<8|(mi))從宏定義可以看出,設備號高位存放著設備的主設備號,低8位存放著設備的次設備號。如果已知設備的設備號,可以利用MAJOR()和MINOR()宏來將該設備的主、次設備號分離出來。該宏定義如下: #defineMAJOR(dev) ((dev)>>8) #defineMINOR(dev) ((dev)&0xff)Linux系統下,有關主設備號的分配原則,可以參看Documentation/Device.txt。二、設備驅動的基礎知識設備號第十頁,共103頁。Linux驅動程序可以通過兩種方式集成到內核中去:一是將其直接編譯到內核;二是將其編寫成模塊,在需要添加某種硬件的時候,內核可以將其調入。在配置Linux內核時,可以選擇“Enableloadablemodulesupport”選項,來支持可加載內核模塊。二、設備驅動的基礎知識內核模塊前一種方法將驅動程序直接寫入內核,其優點是用戶可以隨時地進行調用而無需安裝。但是這樣會大大增加內核占用的空間,導致內核體積較大。

第十一頁,共103頁。后一種方法將驅動程序編寫成模塊供內核有選擇性地加載,雖然會因為尋找驅動模塊而增加系統資源的占用和運行時間,但是與龐大的內核所消耗的資源相比顯得微不足道。另外,這種可加載的內核模塊還可以為軟件開發提供許多便利。當用戶需要對某一硬件驅動程序進行開發或者糾錯時,用戶可以動態地卸載舊的版本并加載新的版本,但是如果用戶的驅動程序已經寫入內核,那么必須對內核進行重新編譯,并且每次對修改后的程序進行測試時,都必須重新啟動系統。顯然,后者在時間和精力上花費更大。如果將驅動程序視為可加載的內核模塊進行開發和配置,用戶就可以將硬件驅動程序作為一種獨立的系統進行升級,而不必頻繁地對內核進行改動了。

二、設備驅動的基礎知識內核模塊第十二頁,共103頁。可加載的內核模塊通常情況下安裝在系統“/lib/modules”目錄的一個子目錄下。我們可以通過通過模塊加載或者卸載命令來對模塊進行管理。二、設備驅動的基礎知識內核模塊常用的模塊相關命令列表另外,注意Linux2.6內核對可加載內核模塊規定了新的命名方法,使用的是“.ko”擴展名,而不是Linux2.4內核采用的“.o”擴展名。

第十三頁,共103頁。Linux設備驅動程序包含中斷處理程序和設備服務子程序兩部分:設備服務子程序包含了所有與設備操作相關的處理代碼。它從面向用戶進程的設備文件系統中接受用戶命令,并對設備控制器執行操作。這樣,設備驅動程序屏蔽了設備的特殊性,使用戶可以像對待文件一樣操作設備。設備控制器需要獲得系統服務時有兩種方式:查詢和中斷。因為Linux下的設備驅動程序是內核的一部分,在設備查詢期間系統不能運行其他代碼,查詢方式的工作效率比較低,所以只有少數設備如軟盤驅動程序采取這種方式,大多設備以中斷方式向設備驅動程序發出輸入/輸出請求。Linux的輸入/輸出系統的各層次結構和功能,如圖:二、設備驅動的基礎知識驅動層次結構Linux輸入/輸出系統層次結構和功能

第十四頁,共103頁。Linux設備驅動程序與外界的接口可以分為如下三個部分:驅動程序與操作系統內核的接口:這是通過數據結構(在本書后面會有詳細介紹)來完成的。驅動程序與系統引導的接口:這部分利用驅動程序對設備進行初始化。驅動程序與設備的接口:這部分描述了驅動程序如何與設備進行交互,這與具體設備密切相關。二、設備驅動的基礎知識設備驅動程序與外界接口第十五頁,共103頁。它們之間的相互關系如下圖:二、設備驅動的基礎知識設備驅動程序與外界接口第十六頁,共103頁。二、設備驅動的基礎知識設備驅動程序的特點綜上所述,Linux中的設備驅動程序有如下特點:內核代碼 設備驅動程序是內核的一部分,如果驅動程序出錯,則可能導致系統崩潰。內核接口 設備驅動程序必須為內核或者其子系統提供一個標準接口。比如,一個終端驅動程序必須為內核提供一個文件I/O接口;一個SCSI設備驅動程序應該為SCSI子系統提供一個SCSI設備接口,同時SCSI子系統也必須為內核提供文件的I/O接口及緩沖區。內核機制和服務 設備驅動程序使用一些標準的內核服務,如內存分配等。可裝載 大多數的Linux操作系統設備驅動程序都可以在需要時裝載進內核,在不需要時從內核中卸載。可設置 Linux操作系統設備驅動程序可以集成為內核的一部分,并可以根據需要把其中的某一部分集成到內核中,這只需要在系統編譯時進行相應的設置即可。動態性 在系統啟動且各個設備驅動程序初始化后,驅動程序將維護其控制的設備。如果該設備驅動程序控制的設備不存在也不影響系統的運行,那么此時的設備驅動程序只是多占用了一點系統內存而已。第十七頁,共103頁。可裝載 大多數的Linux操作系統設備驅動程序都可以在需要時裝載進內核,在不需要時從內核中卸載。可設置 Linux操作系統設備驅動程序可以集成為內核的一部分,并可以根據需要把其中的某一部分集成到內核中,這只需要在系統編譯時進行相應的設置即可。動態性 在系統啟動且各個設備驅動程序初始化后,驅動程序將維護其控制的設備。如果該設備驅動程序控制的設備不存在也不影響系統的運行,那么此時的設備驅動程序只是多占用了一點系統內存而已。二、設備驅動的基礎知識設備驅動程序的特點第十八頁,共103頁。以往在開發應用程序時都有一個main函數作為程序的入口點,而在驅動開發時卻沒有main函數,模塊在調用insmod命令時被加載,此時的入口點是init_module函數,通常在該函數中完成設備的注冊。同樣,模塊在調用rmmod函數時被卸載,此時的入口點是cleanup_module函數,在該函數中完成設備的卸載。在設備完成注冊加載之后,用戶的應用程序就可以對該設備進行一定的操作,如read、write等,而驅動程序就是用于實現這些操作,在用戶應用程序調用相應入口函數時執行相關的操作,init_module入口點函數則不需要完成其他如read、write之類功能。二、設備驅動的基礎知識驅動開發流程第十九頁,共103頁。Linux驅動程序編寫流程如圖:二、設備驅動的基礎知識驅動開發流程第二十頁,共103頁。Linux內核是“單內核”結構,這個單內核由很多模塊構成,每個模塊完成內核一部分的功能,比如TCP/IP協議棧模塊完成網絡協議功能,文件系統模塊完成文件管理功能等等。使用模塊的好處是可以根據用戶的需要隨意裁減Linux系統,使得整個系統恰好適應產品的需要。正是具有這樣的特點,Linux被廣泛應用在嵌入式產品設計中。但是這里我們要指出的是Linux內核中的模塊機制不同于微內核中的模塊機制。在微內核中,模塊處于用戶空間;而在Linux中,模塊位于內核中,用戶可以通過insmod等工具將一段代碼加入到內核中,也可以在不需要它的時候,調用rmmod工具將其調出內核。

三、模塊編程模塊與內核第二十一頁,共103頁。內核和模塊之間的關系見圖:三、模塊編程模塊與內核第二十二頁,共103頁。在Linux內核中使用模的好處是:模塊化編程的需要,降低開發和維護成本。增強系統的靈活性,使得修改一些內核功能而不必重新編譯內核和重啟系統。降低內核編程的復雜性,使入門門檻降低。

三、模塊編程模塊與內核在進行模塊設計的時候,必須遵循Linux的標準,否則無法通過Linux的insmod運載工具將其加入到內核中。簡單來說,一個最基本的內核模塊一般都包含有兩個函數,一個是初始化函數(比如下面例子中hello_init),一個是卸載函數(hello_exit),當然也可以沒有任何函數,只是提供一些變量。一般來說,模塊代碼中必須具有初始化函數和卸載函數,除此之外,還可以包含其它函數。宏module_init和module_exit用于注冊初始化函數和卸載函數。

第二十三頁,共103頁。首先,新建一個文件,保存為hello.c,在文件中輸入源碼。三、模塊編程實例該模塊主要是用來說明Linux內核驅動模塊程序的一些基本特點。該內核模塊中主要定義了hello_init和hello_exit兩個函數,并且在文件的后面用module_init和module_exit包含起來。module_init和module_exit為內核特殊的宏,用來定義模塊被裝載和卸載時分別調用的函數。當模塊被裝載進內核時將自動調用hello_init函數,當該模塊被卸載時將自動調用hello_exit函數。最后一行用MODULE_LICENSE宏來聲明該模塊的許可協議,該模塊聲明為GPL(GeneralPublicLicense)許可協議。第二十四頁,共103頁。該模塊主要是用來說明Linux內核驅動模塊程序的一些基本特點。該內核模塊中主要定義了hello_init和hello_exit兩個函數,并且在文件的后面用module_init和module_exit包含起來。module_init和module_exit為內核特殊的宏,用來定義模塊被裝載和卸載時分別調用的函數。當模塊被裝載進內核時將自動調用hello_init函數,當該模塊被卸載時將自動調用hello_exit函數。最后一行用MODULE_LICENSE宏來聲明該模塊的許可協議,該模塊聲明為GPL(GeneralPublicLicense)許可協議。三、模塊編程編寫makefile第二十五頁,共103頁。為這個模塊程序寫一個makefile文件,這個文件是基于PC機器上的模塊,所以Makefile內容如下:

MODFLAGS=-Wall-DMODULE-D__KERNEL__-DLINUX-I/usr/src/linux-2.6.29/include-c–ohello.o:hello.cgcc$(MODFLAGS)$@$<三、模塊編程編寫makefile第二十六頁,共103頁。程序說明:-D__KERNEL__:該參數告訴編譯器此代碼將在內核模塊中運行,而不是用戶進程。-DMODULE:該參數告訴編譯器要給出適當的內核模塊的定義。-DLINUX:從技術上講,這個標志不是必要的。但是,如果希望寫一個比較正規的內核模塊,在多個操作系統上編譯,這個標志將會使你感到方便。它可以允許你在獨立于操作系統的部分進行常規的編譯。另外,此處用-I參數告訴編譯器使用/usr/src/linux-2.6.29/include目錄下的頭文件,如果不加該參數,則gcc默認使用/usr/include下的頭文件,這樣會產生版本等問題,這里不同的版本可能會不一樣。三、模塊編程編寫makefile第二十七頁,共103頁。基于ARM的模塊,Makefile內容如下: MODFLAGS=-Wall-DMODULE-D__KERNEL__-DLINUX-I/usr/src/linux-2.6.29/include-c–o hello.o:hello.c arm-linux-gcc$(MODFLAGS)$@$<三、模塊編程編寫makefile第二十八頁,共103頁。(1)insmodhello.o當我們使用insmodhello.o命令加載hello模塊時,內核至少完成下列幾件任務:將hello.o代碼搬運到內核中創建structmodule變量,并為相應成員變量賦值,其中name為模塊名hello,init函數指針指向hello_init函數,cleanup函數指針指向hello_exit函數執行init函數指針所指向的函數,具體命令如下 insmodhello.o三、模塊編程模塊加載第二十九頁,共103頁。(2)查看模塊查看模塊可以使用lsmod命令,當使用lsmod命令時,將會掃描整個模塊鏈,并將其信息輸出。三、模塊編程模塊加載(3)rmmodhello如果要將在內核中的模塊卸載,使用rmmod命令可以完成這個工作。具體命令如下:#rmmodhello.o上面刪除模塊命令,將完成下面幾個任務:執行cleanup函數指針所指向的函數,主要是完成清理干凈該模塊在內核中的垃圾將hello模塊代碼清除出內核將描述hello模塊的變量從鏈表中刪除第三十頁,共103頁。比較常用信息常常包括:作者、描述、版權等,可以使用如下宏進行定義。

MODULE_AUTHOR("author"); MODULE_DESCRIPTION("thedescription"); MODULE_LICENSE("GPL"); MODULE_SUPPORTED_DEVICE("dev");//設備驅動程序所支持的設備。比較常用的Freelicense有"GPL","GPLv2","GPLandadditionalrights","DualBSD/GPL","DualMPL/GPL"。

三、模塊編程模塊其他信息第三十一頁,共103頁。用戶空間的應用程序可以接受用戶的參數,那么將模塊辦運到內核中時,也是可以帶進參數的,只是方式有些不同而已。相關的宏主要有:

MODULE_PARM(var,type); MODULE_PARM_DESC(var,"thedescriptionofthevar");

模塊參數的類型(即MODULE_PARM中的type)有一下幾種:

bbyte(unsignedchar) hshort iint llong sstring(char*)這些參數最好有默認值,如果有些必要參數用戶沒有設置可以通過在module_init指定的init函數返回負值來拒絕模塊的加載。LKM還支持數組類型的模塊,如果在類型符號前加上數字n則表示最大程度為n的數組,用“-”隔開的數字分別代表最小和最大的數組長度。三、模塊編程模塊參數第三十二頁,共103頁。例如如下參數:

MODULE_PARM(var,"4i");//最大長度為4的整形數組

MODULE_PARM(var,"2-6i");//最小長度為2,最大長度為6的整形數組使用insmod傳入參數:

insmodvariable=value[,value2...]...注:value可以用引號括起來,也可以不用。但是有一點“=”前后不能留有空格,并且value中也不能有空格。三、模塊編程模塊參數第三十三頁,共103頁。編寫Linux字符設備驅動程序需要熟悉三個重要的數據結構:(文件操作)、file(文件)和inode(節點)。這三個數據結構被定義在“include/linux/fs.h”文件中,是編寫字符設備驅動時經常用到的。由于用戶進程是通過設備文件同硬件打交道,對設備文件的操作方式Linux同樣也做出了一系列的規范。四、字符設備驅動相關數據結構第三十四頁,共103頁。首先,我們來了解一個非常重要的數據結構:,它存儲驅動內核模塊提供的對設備進行這種操作的函數指針,也就是設備驅動程序的入口點。它是一個在<linux/fs.h>中定義的structfile結構,這是一個內核結構,不會出現在用戶空間的程序中,它定義了常見文件I/O函數的入口。四、字符設備驅動第三十五頁,共103頁。在2.6內核版本中的具體定義如下: struct{ structmodule*owner; loff_t(*llseek)(structfile*,loff_t,int); ssize_t(*read)(structfile*,char__user*,size_t,loff_t*); ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*); ssize_t(*aio_read)(structkiocb*,conststructiovec*,unsignedlong,loff_t); ssize_t(*aio_write)(structkiocb*,conststructiovec*,unsignedlong,loff_t); int(*readdir)(structfile*,void*,filldir_t); unsignedint(*poll)(structfile*,structpoll_table_struct*); int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong); int(*mmap)(structfile*,structvm_area_struct*);

int(*open)(structinode*,structfile*); int(*flush)(structfile*,fl_owner_tid); int(*release)(structinode*,structfile*); int(*fsync)(structfile*,structdentry*,intdatasync); int(*aio_fsync)(structkiocb*,intdatasync); int(*fasync)(int,structfile*,int); int(*lock)(structfile*,int,struct*); ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int); unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong); int(*check_flags)(int); int(*dir_notify)(structfile*filp,unsignedlongarg); int(*flock)(structfile*,int,struct*); };四、字符設備驅動第三十六頁,共103頁。structmodule*owner;該成員是結構中唯一一個不是聲明操作的成員,它是一個指向擁有這個模塊的指針,該成員用來在它的操作還在使用是不允許卸載該模塊,通常情況下都被簡單初始化為THIS_MODULE。loff_t(*llseek)(structfile*,loff_t,int);方法llseek用來修改一個文件的當前讀寫位置,并將新位置做為(正的)返回值返回。出錯時返回一個負的返回值。如果驅動程序沒有設置這個函數,相對與文件尾的定位操作失敗,其他定位操作修改file結構(在“file結構”中介紹)中的位置計數器,并成功返回。四、字符設備驅動第三十七頁,共103頁。ssize_t(*read)(structfile*,char__user*,size_t,loff_t*);用來從設備中讀取數據。當其為NULL指針時將引起read系統調用返回-EINVAL(“非法參數”)。函數返回一個非負值表示成功的讀取了多少字節。其中ssize_t為int或者long型,和平臺有關。_user用來聲明為用戶空間。ssize_t(*write)(structfile*,constchar__user*,size_t,loff_t*);向設備發送數據。如果沒有這個函數,write系統調用向調用程序返回一個-EINVAL。注意。如果返回值非負,它就表示成功地寫入的字節數。四、字符設備驅動第三十八頁,共103頁。ssize_t(*aio_read)(structkiocb*,conststructiovec*,unsignedlong,loff_t);該操作用來初始化一個異步的讀操作,即當一個讀操作還沒有完成時也許這個函數已經返回。當該操作為空時,它將由read(同步)操作代替。ssize_t(*aio_write)(structkiocb*,conststructiovec*,unsignedlong,loff_t);該操作用來初始化一個異步寫操作,當該操作為空時,調用write操作。int(*readdir)(structfile*,void*,filldir_t);對于設備節點來說,這個字段應該為NULL;它僅用于目錄。四、字符設備驅動第三十九頁,共103頁。unsignedint(*poll)(structfile*,structpoll_table_struct*);該操作用來查詢一個或者多個文件描述符的讀寫是否會阻塞。Poll方法返回一位掩碼用來指示是否非阻塞的讀或寫是可能的,并且提供給內核信息涌來時調用進程sleep直到I/O端口變為可用。如果一個設備驅動的Poll方法為空,則設備默認為不可阻塞的可讀和可寫操作。int(*ioctl)(structinode*,structfile*,unsignedint,unsignedlong);系統調用ioctl提供一中調用設備相關命令的方法(如軟盤的格式化一個磁道,這既不是讀操作也不是寫操作)。另外,內核還識別一部分ioctl命令,而不必調用fops表中的ioctl。如果設備不提供ioctl入口點,對于任何內核沒有定義的請求,ioctl系統調用將返回-EINVAL。當調用成功時,返回給調用程序一個非負返回值。四、字符設備驅動第四十頁,共103頁。int(*mmap)(structfile*,structvm_area_struct*);mmap用來將設備內存映射到進程內存中。如果設備不支持這個方法,mmap系統調用將返回-ENODEV錯誤信息。int(*open)(structinode*,structfile*);盡管這總是操作在設備節點上的第一個操作,然而并不要求驅動程序一定要聲明這個方法。如果該項為NULL,設備的打開操作永遠成功,但系統不會通知你的驅動程序。 int(*flush)(structfile*,fl_owner_tid);該操作用來執行和等待設備未完成的操作,目前該方法很少使用,不過SCSI磁帶驅動使用了它,用來確保所有寫的數據在設備關閉前已經寫到磁帶上。如果flush為空,內核簡單地忽略應用程序的請求。四、字符設備驅動第四十一頁,共103頁。int(*release)(structinode*,structfile*);當節點被關閉時調用這個操作。與open相仿,release也可以沒有。在2.0和更早的核心中,close系統調用從不失敗。int(*fsync)(structfile*,structdentry*,intdatasync);刷新設備。如果驅動程序不支持,fsync系統調用返回-EINVAL。int(*fasync)(int,structfile*,int);這個操作用來通知設備它的FASYNC標志的變化。如果設備不支持異步觸發,該字段可以是NULL。四、字符設備驅動第四十二頁,共103頁。int(*aio_fsync)(structkiocb*,intdatasync);該操作為fsync的異步版本。int(*lock)(structfile*,int,struct*);該操作用來對文件實行加鎖,加鎖對常規文件是必不可少的特性,但是設備驅動很少有實現該操作的。ssize_t(*sendpage)(structfile*,structpage*,int,size_t,loff_t*,int);該操作用來由內核調用來發送數據,一次一頁到對應的文件。設備驅動程序實際上不實現sendpage方法。四、字符設備驅動第四十三頁,共103頁。unsignedlong(*get_unmapped_area)(structfile*,unsignedlong,unsignedlong,unsignedlong,unsignedlong);該操作用來在進程地址空間找一個合適的位置來映射在底層設備上的內存段中。該方法是使驅動能強制滿足特殊設備的對齊請求。通常情況下,該方法設置為空。int(*check_flags)(int);該操作循序模塊檢查傳遞給fnctl(F_SETFL…)調用的標志。通常情況下該方法設置為空。四、字符設備驅動第四十四頁,共103頁。int(*dir_notify)(structfile*filp,unsignedlongarg);該操作只對文件系統有用,該方法在應用程序使用fcntl函數來請求目錄改變通知時調用。設備驅動程序不需要實現dir_notify方法。int(*flock)(structfile*,int,struct*);該操作用來對文件設備加鎖,但是基本上沒有驅動程序實現此操作。四、字符設備驅動第四十五頁,共103頁。結構體的確包含了很多操作,但是基本上用到的不多。例如Linux中斷實例中的文件操作定義如下:

staticstructkey_fops { owner:THIS_MODULE, read: key_read, open:key_open, release:key_release, };從中我們可以看出,該設備驅動模塊只實現了read、open和release三個操作,這三個操作所對應的實現函數分別是:key_read、key_open和key_release,其它操作都沒有實現。四、字符設備驅動第四十六頁,共103頁。file結構,即文件結構,它不同于應用程序空間的FILE指針,FILE指針定義了在C庫中而不會出現在內核代碼中,而structfile只出現在內核代碼中,從不出現在用戶程序中。

file四、字符設備驅動第四十七頁,共103頁。file結構體在Linux2.6版本的內核中的定義如下: structfile{ structlist_headfu_list; structdentry *f_dentry; structvfsmount *f_vfsmnt; conststruct*f_op; atomic_long_tf_count; unsignedintf_flags; fmode_tf_mode; int f_error; loff_tf_pos; structfown_structf_owner; unsignedintf_uid,f_gid; structf_ra; u64f_version;file四、字符設備驅動第四十八頁,共103頁。#ifdefCONFIG_SECURITY void*f_security; #endif/*neededforttydriver,andmaybeothers*/void*private_data;

#ifdefCONFIG_EPOLL/*Usedbyfs/eventpoll.ctolinkallthehookstothisfile*/structlist_headf_ep_links;spinlock_tf_ep_lock;#endif/*#ifdefCONFIG_EPOLL*/structaddress_space*f_mapping;#ifdefCONFIG_DEBUG_WRITECOUNTunsignedlongf_mnt_write_state;#endif};file四、字符設備驅動第四十九頁,共103頁。structdentry*f_dentry;該成員是文件對應的目錄項結構。除了使用flip->f_dentry->d_inode的方法訪問索引節點結構外,設備驅動開發人員一般無需關心dentry結構。conststruct*f_op;該成員定義與文件有關聯的操作集合,也就是前面中介紹的文件操作。內核在執行open操作時對這個指針賦值,以后需要處理這些操作時就讀這個指針。unsignedintf_flags;該成員為文件標志,如O_RDONLY(只讀)、O_NONBLOCK(非阻塞)和O_SYNC(同步)。驅動程序應該檢查O_NONBLOCK標志判斷是否為非阻塞操作請求。這里注意,讀寫權限通過f_mode成員檢查而不是f_flags。file四、字符設備驅動第五十頁,共103頁。fmode_tf_mode;該成員用來確定文件是可讀的,可寫的或者可讀可寫的,通過位FMODE_READ和FMODE_WRITE實現。loff_tf_pos;該成員用來確定當前的讀寫位置。如果需要知道當前在文件中的位置,驅動程序可以讀該值,但是不應該改變該值。void*private_data;該成員是跨系統調用時保存狀態信息非常有用的資源。驅動程序可以用該字段指向已分配的數據,但一定要在內核銷毀file結構前在release方法中釋放內存。file四、字符設備驅動第五十一頁,共103頁。file文件結構代表一個打開的文件描述符,它不是專門給驅動程序使用,系統中每個打開的文件在內核中都有一個關聯的structfile。它由內核在open時創建,并傳遞給文件上操作的任何函數,直達最后關閉。當文件的所有實例都關閉后,內核釋放該數據結構。file四、字符設備驅動第五十二頁,共103頁。內核中inode(節點)結構表示具體的文件,而用file結構表示打開的文件描述符。對于單個文件,可能會有許多個表示打開的文件描述符file結構,但是它們都是指向單個的inode結構,所以file結構和inode結構是不同的。inode結構中包含了大量有關文件的信息,但通常情況下對設備驅動程序開發有用的成員有下面兩個: dev_ti_rdev;該成員表示設備文件的inode結構,它包含了真正的設備編號。 structcdev*i_cdev;該成員表示字符設備內核的內部結構,當inode指向一個字符設備文件時,該成員包含了指向structcdev結構的指針,其中cdev結構是字符設備結構體。inode四、字符設備驅動第五十三頁,共103頁。在fs/devices.c文件中,會有如下一全局變量定義: staticstructdevice_structchrdevs[MAX_CHRDEV];實際上全局數組chrdevs是所有字符設備管理的入口,數組的下標為具體某個字符設備的設備號,每個數組元素描述了具體設備的設備驅動。Chrdevs向量表中的每一個條目,一個device_struct數據結構,包括兩個元素:一個登記的設備驅動程序的名稱的指針和一個指向一組文件操作的指針。這塊文件操作本身位于這個設備的字符設備驅動程序中,每一個都處理特定的文件操作比如打開、讀、寫和關閉。device_struct四、字符設備驅動第五十四頁,共103頁。編寫一個字符設備驅動,主要是下面幾步:編寫硬件接口函數建立文件系統與設備驅動程序的接口變量,類型為struct結構體,并初始化該變量注冊設備到chrdevs全局數組中以模塊方式編譯驅動源碼,并將其加載到內核中創建設備節點編寫應用程序訪問底層設備字符設備驅動開發流程四、字符設備驅動第五十五頁,共103頁。其流程圖如下:字符設備驅動開發流程四、字符設備驅動第五十六頁,共103頁。編寫硬件接口函數是設備驅動的主要工作,也是重點和難點。做這部分工作最核心的是要掌握硬件的工作原理,從本質上來說,這部分的內容就是前面接口部分的內容,所以基于操作系統的驅動程序設計就是在無操作系統下的硬件接口函數加上操作系統的外套而已。編寫硬件接口函數四、字符設備驅動第五十七頁,共103頁。設備要使用必須首先打開設備,在Linux中,打開設備使用open函數:函數原型: int(*open)(structinode*inode,structfile*file)參數: inode:節點 file:文件返回值:如果成功返回該設備的句柄頭文件:#include<linux/fs.h>打開設備:open函數四、字符設備驅動第五十八頁,共103頁。對于不同的設備來說,open函數完成的功能也各不相同,但通常來說要完成如下幾件工作:增加設備使用計數器:如果同一設備可以被多個應用程序同時打開使用,則其中任一進程想要關閉該設備時,必須確保其它設備沒有使用該設備。模塊計數器相關宏可以這個功能。 MOD_INC_USE_COUNT:計數器加一 MOD_DEC_USE_COUNT:計數器減一 MOD_IN_USE:計數器非零時返回真檢查特定設備的特殊情況初始化設備完成其它功能打開設備:open函數四、字符設備驅動第五十九頁,共103頁。當一個進程不使用由它打開的設備時,可以將其釋放,釋放函數的接口原型為realse,完成下面幾個任務:遞減計數器如果沒有進程使用該設備,則將該設備關閉如果在打開該設備時申請了堆中的內存,則釋放該內存釋放設備:release函數四、字符設備驅動第六十頁,共103頁。在打開設備或釋放設備時,有時會申請內存或釋放內存,由于設備驅動位于內核,我們必須使用基于內核內存的函數,而不能使用malloc()和free()函數來獲得內存或者釋放內存。kmalloc函數函數原型: void*kmalloc(unsignedintlen,intflags)操縱內存:kmalloc和kfree等函數四、字符設備驅動第六十一頁,共103頁。參數:len:申請內存大小(以字節為單位)flags: GFP_KERNEL:分配內核內存時通常使用該參數,但可 能會引起睡眠 GFP_BUFFER:用于管理緩沖區高速緩存 GFP_ATOMIC:為中斷處理程序或其它運行于進程上下 文之外的代碼分配內存,不會引起睡眠 GFP_DMA:分配DMA內存 GFP_HIGHUSER:優先高端內存分配 GFP_HIGHMEM:從高端內存區分配 GFP_USER:用戶分配內存返回值:成功:分配的內核內存地址錯誤:-EFAULT頭文件:#include<linux/malloc.h>操縱內存:kmalloc和kfree等函數四、字符設備驅動第六十二頁,共103頁。函數原型: voidkfree(void*ptr)參數: ptr:要釋放的內存指針返回值:成功:無返回值錯誤:-EFAULT頭文件:#include<linux/malloc.h>kfree函數四、字符設備驅動第六十三頁,共103頁。函數原型: ssize_t(*read)(structfile*file,char*buff,size_tcount,loff_t*offp)參數:file:文件指針buff:指向用戶緩沖區,即將內核數據存放的目的地址count:要讀取的數據長度offp:讀指針位置返回值: 如果成功返回讀取的字節數頭文件:#include<linux/fs.h>read函數四、字符設備驅動第六十四頁,共103頁。函數原型:

ssize_t(*write)(structfile*file,constchar*buffer,size_tcount,loff_t*ppos)參數:file:文件指針buff:指向用戶緩沖區,即要讀取的數據源地址count:要讀取的數據長度offp:讀指針位置返回值: 如果成功返回寫入的字節數頭文件:#include<linux/fs.h>write函數四、字符設備驅動第六十五頁,共103頁。函數原型:

unsignedlongcopy_to_user(void*to,constvoid*from,unsignedlongcount) unsignedlongcopy_from_user(void*to,constvoid*from,unsignedlongcount)參數:to:指向目的緩沖區地址from:指向數據源緩沖區地址count:傳輸的數據長度返回值:如果成功返回傳輸字節數頭文件:#include<linux/fs.h>copy_from_user/copy_to_user函數四、字符設備驅動第六十六頁,共103頁。設備驅動程序的格式如下,比方說要得到watchdog_open函數,可定義如下: staticstructwatchdog_fops={ open: watchdog_open, release: watchdog_release, write: watchdog_write, };這樣open函數指針指向watchdog_open函數,release函數指針指向watchdog_release函數,write函數指針指向watchdog_write函數。建立文件系統與設備驅動程序的接口定義四、字符設備驅動第六十七頁,共103頁。設備注冊使用函數register_chrdev,該函數執行后將設備在chrdevs[]數組中進行登記,如果用戶要注銷設備(即將設備從chrdevs[]數組中刪除),可以調用unregister_chrdev函數。注冊/注銷設備四、字符設備驅動第六十八頁,共103頁。函數原型:

intregister_chrdev(unsignedintmajor,constchar*name,struct*fops)參數:major:設備驅動程序向系統申請的主設備號,如果為0則動態分配一個主設備號name:設備名fops:指向文件系統與設備驅動程序的接口變量返回值:成功:返回分配設備的主設備號,可以在/proc/devices文件中查詢到出錯:返回-1頭文件:#include<linux/fs.h>register_chrdev函數四、字符設備驅動第六十九頁,共103頁。函數原型:

intunregister_chrdev(unsignedintmajor,constchar*name)參數:major:設備驅動程序向系統申請的主設備號,如果為0則動態分配一個主設備號name:設備名fops:指向文件系統與設備驅動程序的接口變量返回值:成功:返回值0出錯:返回值-1頭文件:#include<linux/fs.h>unregister_chrdev函數四、字符設備驅動第七十頁,共103頁。如果以模塊的方式進行編譯,則編寫Makefie,內容如下:

CROSS=arm-linux- MODFLAGS=-Wall-DMODULE-D__KERNEL__-DLINUX\ -I/root/Myjob/s3c2440_kernel2.4.18_rel/include-c–o watchdog.o:watchdog.c $(CROSS)gcc $(MODFLAGS)$@$< clean: rm-rfwatchdog.o 只需make后生成watchdog.o文件,調用insmod命令進行加載:

insmodwatchdog.o模塊編譯并加載四、字符設備驅動第七十一頁,共103頁。到上一步為止,我們已經完整編寫了看門狗設備驅動,并將其代碼搬運到內核中,下面就可以以文件的方式來訪問這些底層接口函數。所以我們必須創建設備文件,并將其和內核中的設備驅動關聯到一起,這樣用戶就可以在用戶空間中通過訪問設備文件來控制底層硬件工作的目的。創建設備節點格式為: mknod設備名設備類型主設備號次設備號因此創建看門狗設備文件: mknod/dev/watchdogc2340創建設備節點四、字符設備驅動第七十二頁,共103頁。1、ioctl基本概念ioctl是設備驅動程序中對設備的I/O通道進行管理的函數。所謂對I/O通道進行管理,就是對設備的一些特性進行控制,例如串口的傳輸波特率、馬達的轉速等等。ioctl函數調用形式如下:

intioctl(intfd,indcmd,…);其中fd就是用戶程序打開設備時使用open函數返回的文件標示符,cmd就是用戶程序對設備的控制命令,至于后面的省略號,那是一些補充參數,一般最多一個,有或沒有是和cmd的意義相關的。字符設備驅動擴展操作四、字符設備驅動第七十三頁,共103頁。2、構建命令碼在驅動程序中實現的ioctl函數體內,實際上有一個switch{case}結構,每一個case對應一個命令碼,做出一些相應的操作。怎么實現這些操作,程序員可以根據設備來進行具體編寫。但是命令碼是如何組織的呢?這很關鍵。因為在ioctl中命令碼是唯一聯系用戶程序命令和驅動程序支持的途徑。

字符設備驅動擴展操作四、字符設備驅動第七十四頁,共103頁。命令碼的組織是有一些講究的,因為一定要做到命令和設備是一一對應的,這樣才不會將正確的命令發給錯誤的設備,或者是把錯誤的命令發給正確的設備,或者是把錯誤的命令發給錯誤的設備。這些錯誤都會導致不可預料的事情發生,而當程序員發現了這些奇怪的事情的時候,再來調試程序查找錯誤,那將是非常困難的事情。字符設備驅動擴展操作四、字符設備驅動第七十五頁,共103頁。可以看出,一個命令碼實質上是一個整數形式的命令。但是命令碼非常的不直觀,所以LinuxKernel中提供了一些宏,這些宏可根據便于理解的字符串生成命令碼,或者是從命令碼得到一些用戶可以理解的字符串以標明這個命令對應的設備類型、設備序列號、數據傳送方向和數據傳輸尺寸。

在Linux內核中命令碼采用如下表定義方式:字符設備驅動擴展操作四、字符設備驅動第七十六頁,共103頁。具體相關的宏如下:

//include/ioctl.h #define_IOC(dir,type,nr,size)\ (((dir)<<_IOC_DIRSHIFT)|\ ((type)<<_IOC_TYPESHIFT)|\ ((nr)<<_IOC_NRSHIFT)|\ ((size)<<_IOC_SIZESHIFT)) /*usedtocreatenumbers*/ #define_IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0) #define_IOR(type,nr,size _IOC(_IOC_READ,(type),(nr),sizeof(size)) #define_IOW(type,nr,size _IOC(_IOC_WRITE,(type),(nr),sizeof(size)) #define_IOWR(type,nr,size)_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size)) 其中_IO宏用于生成沒有傳輸方向的命令碼,_IOR宏用于生成讀命令碼,_IOW宏用于生成寫命令碼,_IOWR宏用于生成雙向傳輸的命令碼。字符設備驅動擴展操作四、字符設備驅動第七十七頁,共103頁。1.net_device數據結構在Linux系統中,描述網絡設備的數據結構為structnet_device,該結構體定義文件位于include/linux/netdevice.h文件中,是網絡驅動程序層最核心的一個結構體,值得讀者細細品位,但并不要求大家記住其中的每個細節。

五、網絡設備驅動基本概念第七十八頁,共103頁。(1)全局信息全局信息的成員主要是設備名稱、設備狀態、下一設備指針和初始函數等。下面我們詳細介紹這幾個成員變量。設備名稱全局信息的第一個成員是設備的名稱,在net_device結構體中定義為: char name[IFNAMSIZ];注:順便提一下,配置網絡IP地址經常使用的一條命令:

ifconfigeth0192.168.0.213此處eth0就為第一塊網卡的設備名稱。通常在給name賦值時為0(NULL字符)或空格,如果這樣,register_netdev將給它分配名字ethn,n取合適的值,如第一塊網卡就為eth0,第二塊網卡取名為eth1,依次類推。五、網絡設備驅動net_device數據結構第七十九頁,共103頁。設備狀態接下來很重要的成員是設備狀態,該成員定義如下: unsignedlongstate;這個成員包含許多標志,其實驅動程序通常無需直接操作這些標志,它可以通過內核提供的一組工具函數來訪問。下一設備指針這個成員也很重要,在net_device結構體中定義如下: structnet_device *next;該成員表示全局鏈表下一個設備的指針,因為所有的網絡設備都可以通過next指針連接成一條鏈。值得注意的是驅動程序不應該修改這個成員。五、網絡設備驅動net_device數據結構第八十頁,共103頁。初始化函數如果該指針被設置,則register_netdev()將調用該函數完成對net_device結構的初始化。init函數指針指向網卡。該函數定義格式為: int(*init)(structnet_device*dev);但是現在很多網絡驅動程序不再使用這個函數,它們通常在注冊接口前就直接完成初始化工作。五、網絡設備驅動net_device數據結構第八十一頁,共103頁。(2)硬件信息硬件信息都是跟底層硬件相關的。主要的成員如下:內存起始和中止地址這些成員網絡數據包傳輸和接收數據的內存起始地址和中止地址。其中,mem成員用于傳輸內存,rmem成員用于接收內存。其定義如下:

unsignedlong rmem_end; /*shmem"recv"end */ unsignedlong rmem_start; /*shmem"recv"start */ unsignedlong mem_end; /*sharedmemend */ unsignedlong mem_start; /*sharedmemstart */五、網絡設備驅動net_device數據結構第八十二頁,共103頁。網絡I/O基地址不同的目標板一般使用的I/O口一般都不盡相同,因此網絡接口的I/O基地址的設置和具體的網絡硬件連接密切相關。這個成員的具體定義為: unsignedlongbase_addr; /*deviceI/Oaddress */網絡設備中斷號跟網絡I/O基地址一樣,但不同的是這個中斷號一般可以統一,但它也和具體的網絡硬件連接密切相關。其定義為:

unsignedint irq; /*deviceIRQnumber */五、網絡設備驅動net_device數據結構第八十三頁,共103頁。網絡端口對于網絡很重要的成員還有網絡端口,net_device結構體中定義端口的成員如下: unsignedcharif_port; /*SelectableAUI,TP,..*/它用于指定多端口設備上使用哪個端口。完整的的已知端口類型在<linux/netdevice.h>中定義。DMA通道這個成員為設備分配的DMA通道。該成員只對某些總線有用,比如ISA。該成員定義如下:

unsignedchardma; /*DMAchannel */五、網絡設備驅動net_device數據結構第八十四頁,共103頁。(3)接口信息這類成員不是每種網卡都必須的,如果是以太網卡,大部分接口信息可由ether_setup()函數正確設置。在net_device結構體中相關的成員如下:最大傳輸單元這個成員用于設置最大傳輸單元。該值的設定和數據鏈路層使用的幀類型密切相關,如果為以太幀,則MTU設置為1500個octet。其定義如下: unsignedmtu; /*interfaceMTUvalue*/五、網絡設備驅動net_device數據結構第八十五頁,共103頁。接口硬件類型type成員用于指定接口的硬件類型。以太網中,ARP使用type成員判斷接口所支持的硬件地址類型。已知的硬件地址類型定義在<linux/if_arp.h>中定義。以太網接口類型時,type應設置為ARPHRD_ETHER。其定義如下:

unsignedshorttype;/*interfacehardwaretype */硬件頭長度這個成員用于定義硬件頭的長度。對以太網接口,一般該成員應該被賦值為14(ETH_HLEN),其定義一般如下:

unsignedshorthard_header_len;/*hardwarehdrlength */五、網絡設備驅動net_device數據結構第八十六頁,共103頁。MAC地址這個成員是網絡驅動最重要的幾個變量。該成員指定網卡設備的硬件地址(MAC地址),以太網地址長為六個八元組(我們是指接口板的硬件標志),播送地址由六個0xff八元組組成;ether_setup負責這些值的正確設置。另一方面,設備地址必須以設備特定的方式從接口板中讀出,驅動程序應把它復制到dev_addr。這個硬件地址用來在把包交給驅動程序傳送前產生正確的以太網包頭。其定義如下: unsignedchardev_addr[MAX_ADDR_LEN];/*hwaddress*/接口地址簇這個成員變量,接口并不常查看這個域或者向其賦值,通常為AF_INET。其定義如下: unsignedshortfamily;五、網絡設備驅動net_device數據結構第八十七頁,共103頁。地址相關跟地址相關的成員很多,比較重要的是以下三個。這三個成員分別描述了網卡接口的三個重要地址:接口地址,播送地址,及網絡掩碼。這些值是協議特定的(既它們是“協議地址”);如果dev->family是INET,則它們為IP地址。這些域由ifconfig賦值,對驅動程序是只讀的。它們一般定義為: unsignedlongpa_addr; unsignedlongpa_brdaddr; unsignedlongpa_mask;接口標志跟接口有關的標志位,對于這些標志,有些由核心管理,有些則是在初始化時由接口設置,以確認接口的能力。常用的定義格式為: unsignedshortflags;五、網絡設備驅動net_device數據結構第八十八頁,共103頁。與字符設備和塊設備的情況一樣,每個網絡設備要聲明在其上操作的函數。可以在網絡接口上進行的操作列在下面。一些操作可以留為NULL,還有一些通常不去動它們,因為ether_setup給它們分配合適的方法。一個網絡接口的設備方法可以分為兩類:基本的和可選的。基本的包括那些為訪問接口所需要的;可選的方法實現一些并不嚴格要求的高級功能。五、網絡設備驅動設備方法第八十九頁,共103頁。open接口這個方法是打開硬件接口。只要ifconfig激活一個接口,它就被打開了。open方法要注冊它需要的所有資源(I/O端口,IRQ,DMA,等),打開硬件,增加模塊的使用計數。其格式如下: int(*open)(structdevice*dev);stop接口跟open方法堆成的是stop接口,即終止接口。接口在關閉時就終止了;在打開時進行的操作應被保留。其格式如下: int(*stop)(structdevice*dev);五、網絡設備驅動設備方法第九十頁,共103頁。硬件開始接口這個方法請求一個包的傳送。這個包含在一個套接字緩沖區結構(sk_buff)中。套接字緩沖區在下面介紹。

int(*hard_start_xmit)(s

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論