【轉】如何實現一個文件系統

如何實現一個文件系統

摘要

本章目的是分析在Linux系統中如何實現新的文件系統。在介紹文件系統具體實現前先介紹文件系統的概念和作用,抽象出文件系統概念模型。熟悉文件系統的內涵后,我們再進一步討論Linux系統中文件系統的特殊風格和具體文件系統在Linux中的組成結構,逐步為讀者勾畫出Linux中文件系統工作的全景圖。最后在事例部分,我們將以romfs文件系統作實例,分析實現文件系統的普遍步驟。

什么是文件系統

別混淆“文件系統”

首先要談的概念就是什么是文件系統,它的作用到底是什么。

文件系統的概念雖然許多人都認為是再清晰不過的了,但其實我們往往在談論中或多或少地夸大或縮小了它的實際概念(至少我時常混淆),或者說,有時借用了其它概念,有時說得又不夠全面。

比如在操作系統中,文件系統?這個術語往往既被用來描述磁盤中的物理布局,比如有時我們說磁盤中的“文件系統”是EXT2或說把磁盤格式化成FAT32格式的“文件系統”等——這時所說的“文件系統”是指磁盤數據的物理布局格式;另外,文件系統也被用來描述內核中的邏輯文件結構,比如有時說的“文件系統”的接口或內核支持Ext2等“文件系統”——這時所說的文件系統都是內存中的數據組織結構而并非磁盤物理布局(后面我們將稱呼它為邏輯文件系統);還有些時候說“文件系統”負責管理用戶讀寫文件——這時所說的“文件系統”往往描述操作系統中的“文件管理系統”,也就是文件子系統。

雖然上面我們列舉了混用文件系統的概念的幾種情形,但是卻也不能說上述說法就是錯誤的,因為文件系統概念本身就囊括眾多概念,幾乎可以說在操作系統中自內存管理、系統調度到I/O系統、設備驅動等各個部分都和文件系統聯系密切,有些部分和文件系統甚至未必能明確劃分——所以不能只知道文件系統是系統中數據的存儲結構,一定要全面認識文件系統在操作系統中的角色,才能具備自己開發新文件系統的能力。

文件系統的體系結構

為了澄清文件系統的概念,必須先來看看文件系統在操作系統中處于何種角色,分析文件系統概念的內涵外延。我們先拋開Linux文件系統的實例,而來看看操作系統中文件系統的普遍體系結構,從而增強對文件系統的理論認識。

下面以軟件組成的結構圖[1]的方式來描述文件系統所涉及的內容。

?

?

????????????????????圖?1?:?文件系統體系結構層次圖

針對各層簡要分析如下:

1.首先我們來分析最低層——設備驅動層,該層負責與外設——磁盤等——通訊。文件系統都需要和存儲設備打交道,而系統操作外設時離不開驅動程序。所以內核對文件的最后操作行為就是調用設備驅動程序完成從主存(內存)到輔存(磁盤)的數據傳輸。

文件系統相關的多數設備都屬于塊設備,常見的塊設備驅動程序有磁盤驅動,光驅驅動等,之所以稱它們為塊設備,一個原因是它們讀寫數據都是成塊進行的,但是更重要的原因是它們管理的數據能夠被隨機訪問——不需要像字符設備那樣必須順序訪問。

2.設備驅動層的上一層是物理I/O,該層主要作為計算機外部環境和系統的接口,負責系統和磁盤之間數據塊的交換。它要知道數據塊在磁盤中地存儲位置,也要知道文件數據塊在內存緩沖中的位置,另外,它不需要了解數據或文件的具體結構。可以看到,這層最主要的工作是識別磁盤扇區和內存緩沖塊[2]之間的映射關系。

3.再上層是基礎I/O監督層,該層主要負責選擇文件?I/O需要的設備,調度磁盤請求等工作,另外分配I/O緩沖和磁盤空間也在該層完成。由于塊設備需要隨機訪問數據,而且對速度響應要求較高,所以操作系統不能像對字符設備那樣簡單、直接地發送讀寫請求,而必須對讀寫請求重新優化排序,以能節省磁盤尋址時間,另外也必須對請求提交采取異步調度(尤其寫操作)的方式進行。總而言之,內核必須管理塊設備請求,而這項工作正是由該層負責的。

4.倒數第二層是邏輯I/O,該層允許用戶和應用程序訪問記錄。它提供了通用的記錄(record)I/O操作,同時還維護基本文件數據。為了方便用戶操作和管理文件內容,文件內容往往被組織成記錄形式,所以操作系統為操作文件記錄提供了一個通用的邏輯操作層。

5.和用戶最靠近的是訪問方法層,該層提供了一個從用戶空間到文件系統的標準接口,不同的訪問方法反映了不同的文件結構,也反映了不同的訪問數據和處理數據的方法。這一層我們可以簡單地理解為文件系統給用戶提供的訪問接口——不同的文件格式(如順序存儲格式、索引存儲格式、索引順序存儲格式和哈希存儲格式等)對應不同的文件訪問方法。該層要負責將用戶對文件結構的操作轉化為對記錄的操作。

文件處理流程

對比上面的層次圖我們再來分析一下數據流的處理過程,加深對文件系統的理解。

假如用戶或應用程序操作文件(創建/刪除),首先需要通過文件系統給用戶空間提供的訪問方法層進入文件系統,接著使用邏輯I/O層對記錄進行給定操作,然后記錄將被轉化為文件塊,等待和磁盤交互。這里有兩點需要考慮——第一,磁盤管理(包括在磁盤空閑區分配文件和組織空閑區);第二,調度塊I/O請求——這些是基礎I/O監督層的工作。再下來,文件塊被物理I/O層傳遞給磁盤驅動程序,最后磁盤驅動程序才真正把數據寫入具體的扇區。至此文件操作完畢。

當然上面介紹的層次結構是理想情況下的理論抽象,實際文件系統并非一定要按照上面的層次或結構組織,它們往往簡化或合并了某些層的功能(比如Linux文件系統因為所有文件都被看作字節流,所以不存在記錄,也就沒有必要實現邏輯I/O層,進而也不需要和記錄相關的處理)。但是大體上都需要經過類似的處理。如果從處理對象上和系統獨立性上劃分,文件系統體系結構可以被分為兩大部分:——文件管理部分和操作系統I/O部分。文件管理系統負責操作內存中的文件對象,并按文件的邏輯格式將對文件對象的操作轉化成對文件塊的操作;而操作系統I/O部分負責內存中的塊與物理磁盤中的數據交換。

數據表現形式在文件操作過程中也經歷了幾種變化:用戶訪問文件系統時看到的是字節序列,而在字節序列被寫入磁盤時看到的是內存中文件塊(在緩沖中),在最后將數據寫入磁盤扇區時看到的是磁盤數據塊[3]。

本文所說的實現文件系統主要針對最開始講到的第二種情況——內核中的邏輯文件結構(但其它相關的文件管理系統和文件系統磁盤存儲格式也必須了解),我們用數據處理流圖來分析一下邏輯文件系統的主要功能和在操作系統中所處的地位。

?

?

???????????????????圖?2?文件系統操作流程

其中文件系統接口與物理布局管理是邏輯文件系統要負責的主要功能。

l?????????文件系統接口為用戶提供對文件系統的操作,比如open、close、read、write和訪問控制等,同時也負責處理文件的邏輯結構。

l?????????物理存儲布局管理,如同虛擬內存地址轉化為物理內存地址時,必須處理段頁結構一樣,邏輯文件結構必須轉化到物理磁盤中,所以也要處理物理分區和扇區的實際存儲位置,分配磁盤空間和內存中的緩沖也要在這里被處理。

???????所以說要實現文件系統就必須提供上面提到的兩種功能,缺一不可。

在了解了文件系統的功能后,我們針對Linux操作系統分析具體文件系統如何工作,進而掌握實現一個文件系統所需要的步驟。

Linux?文件系統組成結構

Linux?文件系統的結構除了我們上面所提到的概念結構外,還有兩個最主要的特點,一個是文件系統抽象出了一個通用文件表示層——虛擬文件系統或稱做VFS。另外一個重要特點就是它的文件系統支持動態安裝(或說掛載等),大多數文件系統都可以作為根文件系統的葉子節點被掛在到根文件目錄樹下的子目錄上。另外Linux系統在文件讀寫的I/O操作上也采取了一些先進技術和策略。

我們先從虛擬文件系統入手分析linux文件系統的特性,然后介紹有關文件系統的安裝、注冊和讀寫等概念。

虛擬文件系統

虛擬文件系統為用戶空間程序提供了文件系統接口。系統中所有文件系統不但依賴VFS共存,而且也依靠VFS系統協同工作。通過虛擬文件系統我們可以利用標準的UNIX文件系統調用對不同介質上的不同文件系統進行讀寫操作[4]。

虛擬文件系統的目的是為了屏蔽各種各樣不同文件系統的相異操作形式,使得異構的文件系統可以在統一的形式下,以標準化的方法訪問、操作。實現虛擬文件系統利用的主要思想是引入一個通用文件模型——該模型抽象出了文件系統的所有基本操作(該通用模型源于Unix風格的文件系統),比如讀、寫操作等。同時實際文件系統如果希望利用虛擬文件系統,即被虛擬文件系統支持,也必須將自身的諸如“打開文件”、“讀寫文件”等操作行為以及“什么是文件”,“什么是目錄”等概念“修飾”成虛擬文件系統所要求的(定義的)形式,這樣才能夠被虛擬文件系統支持和使用。

我們可以借用面向對象的一些思想來理解虛擬文件系統,虛擬文件系統好比一個抽象類或接口,它定義(但不實現)了文件系統最常見的操作行為。而具體文件系統好比是具體類,它們是特定文件系統的實例。具體文件系統和虛擬文件系統的關系類似具體類繼承抽象類或實現接口。而用戶看到或操作的都是抽象類或接口,但實際行為卻發生在具體文件系統實例上。至于如何將對虛擬文件系統的操作轉化到對具體文件系統的實例,就要通過注冊具體文件系統到系統,然后再安裝具體文件系統才能實現轉化,這點可以想象成面向對象中的多態概念。

我們舉個實例來說明具體文件系統如何通過虛擬文件系統協同工作。

例如:假設一個用戶輸入以下shell命令:

$?cp?/hda/test1 /removable/test2

其中?/removable是MS-DOS磁盤的一個安裝點,而?/hda?是一個標準的第二擴展文件系統(?Ext2)的目錄。cp命令不用了解test1或test2的具體文件系統,它所看到和操作的對象是VFS。cp首先要從ext3文件系統讀出test1文件,然后寫入MS-DOS文件系統中的test2。VFS會將找到ext3文件系統實例的讀方法,對test1文件進行讀取操作;然后找到MS-DOS(在Linux中稱VFAT)文件系統實例的寫方法,對test2文件進行寫入操作。可以看到?VFS是讀寫操作的統一界面,只要具體文件系統符合VFS所要求的接口,那么就可以毫無障礙地透明通訊了。

下圖給出Linux系統中VFS文件體統和具體文件系統能的層次示意圖

?

圖3?linux系統中VFS和具體文件系統關系圖

Unix風格的文件系統

虛擬文件系統的通用模型源于Unix風格的文件系統,所謂Unix風格是指Unix文件系統傳統上使用了四種和文件系統相關的抽象概念:文件(file)、目錄項(dentry)、索引節點(inode)和安裝點(mount point)。

1.文件—在Unix中的文件都被看做是一有序字節串,它們都有一個方便用戶或系統識別的名稱。另外典型的文件操作有讀、寫、創建和刪除等。

2.目錄項——不要和目錄概念搞混淆,在Linux中目錄被看作文件。而目錄項是文件路徑中的一部分。一個文件路徑的例子是“/home/wolfman/foo”——根目錄是/,目錄home,wolfman和文件foo都是目錄項。

3.索引節點——Unix系統將文件的相關信息(如訪問控制權限、大小、擁有者、創建時間等等信息),有時被稱作文件的元數據(也就是說,數據的數據)-- 存儲到一個單獨的數據結構中,該結構被稱為索引節點(inode)。

4.安裝點——在Unix中,文件系統被安裝在一個特定的安裝點上,所有的已安裝文件系統都作為根文件系統樹中的葉子出現在系統中。

上述概念是Unix文件系統的邏輯數據結構,但相應的Unix文件系統(Ext2等)磁盤布局也實現了部分上述概念,比如文件信息(文件元數據)存儲在磁盤塊中的索引節點上。當文件被載入內存時,內核需要使用磁盤塊中的索引節點來裝配內存中的索引節點。類似行為還有超級塊信息等。

對于非Unix風格文件系統,如FAT或NTFS,要想能被VFS支持,它們的文件系統代碼必須提供這些概念的虛擬形式。比如,即使一個文件系統不支持索引節點,它也必須在內存中裝配起索引節點結構體——如同本身固有一樣。或者,如果一個文件系統將目錄看作是一種特殊對象,那么要想使用VFS,必須將目錄重新表示為文件形式。通常,這種轉換需要在使用現場引入一些特殊處理,使得非Unix文件系統能夠兼容Unix文件系統的使用規則和滿足VFS的需求。通過這些處理,非Unix文件系統便可以和VFS一同工作了,但性能上多少會受一些影響[5]。這點很重要,我們實現自己的文件系統時必須提供(模擬)Unix風格的文件系統的抽象概念。

?

Linux文件系統中使用的對象

Linux文件系統的對象就是指一些數據結構體,之所以稱它們是對象,是因為這些數據結構體不但包含了相關屬性而且還包含了操作自身結構的函數指針,這種將數據和方法進行封裝的思想和面向對象中對象概念一致,所以這里我們就稱它們是對象。

??? Linux文件系統使用大量對象,我們簡要分析一下和VFS相關的對象,除此之外,還有和進程相關的一些其它對象。

VFS相關對象

這里我們不展開討論每個對象,僅僅是為了內容完整性,作簡要說明。

VFS中包含有四個主要的對象類型,它們分別是:

l?????????超級塊對象,它代表特定的已安裝文件系統。

l?????????索引節點對象,它代表特定文件。

l?????????目錄項對象,它代表特定的目錄項。

l?????????文件對象,它代表被進程打開的文件。

每個主要對象中都包含一個操作對象,這些操作對象描述了內核針對主要對象可以使用的方法。最主要的幾種操作對象如下:

super_operations對象,其中包括內核針對特定文件系統所能調用的方法,比如read_inode()和sync_fs()方法等。

inode_operations對象,其中包括內核針對特定文件所能調用的方法,比如create()和link()方法等。

dentry_operations對象,其中包括內核針對特定目錄所能調用的方法,比如d_compare()和d_delete()方法等。

file對象,其中包括進程針對已打開文件所能調用的方法,比如read()和write()方法等。

除了上述的四個主要對象外,VFS還包含了許多對象,比如每個注冊文件系統都是由file_system_type對象表示——描述了文件系統及其能力(比如ext3或XFS);另外每一個安裝點也都用vfsmount對象表示——包含了關于安裝點的信息,如位置和安裝標志等。

其它VFS對象

系統上的每一進程都有自己的打開文件,根文件系統,當前工作目錄,安裝點等等。另外還有幾個數據結構體將VFS層和文件的進程緊密聯系,它們分別是:file_struct?和fs_struct

file_struct結構體由進程描述符中的files項指向。所有包含進程的信息和它的文件描述符都包含在其中。第二個和進程相關的結構體是fs_struct。該結構由進程描述符的fs項指向。它包含文件系統和進程相關的信息。每種結構體的詳細信息不在這里說明了。

緩存對象

除了上述一些結構外,為了縮短文件操作的響應時間,提高系統性能,Linux系統采用了許多緩存對象,例如目錄緩存、頁面緩存和緩沖緩存(已經歸入了頁面緩存),這里我們對緩存做簡單介紹。

頁高速緩存(cache)是?Linux內核實現的一種主要磁盤緩存。其目的是減少磁盤的I/O操作,具體來講就是通過把磁盤中的數據緩存到物理內存中去,把對磁盤的I/O操作變為對物理內存的I/O操作。頁高速緩存是由RAM中的物理頁組成的,緩存中每一頁都對應著磁盤中的多個塊。每當內核開始執行一個頁I/O操作時(通常是對普通文件中頁大小的塊進行磁盤操作),首先會檢查需要的數據是否在高速緩存中,如果在,那么內核就直接使用高速緩存中的數據,從而避免了訪問磁盤。

但我們知道文件系統只能以每次訪問數個塊的形式進行操作。內核執行所有磁盤操作都必須根據塊進行,一個塊包含一個或多個磁盤扇區。為此,內核提供了一個專門結構buffer_head來管理緩沖。緩沖頭[6]的目的是描述磁盤扇區和物理緩沖之間的映射關系和做I/O操作的容器。但是緩沖結構并非獨立存在,而是被包含在頁高速緩存中,而且一個頁高速緩存可以包含多個緩沖。我們將在后面的文件讀寫部分看到數據是如何被從磁盤扇區讀入頁高速緩存中的緩沖中的。

???

文件系統的注冊和安裝

使用文件系統前必須對文件系統進行注冊和安裝,下面分別對這兩種行為做簡要介紹。

文件系統的注冊

VFS要想能將自己定義的接口映射到實際文件系統的專用方法上,必須能夠讓內核識別實際的文件系統,實際文件系統通過將代表自身屬性的文件類型對象(file_system_type)注冊(通過register_filesystem()函數)到內核,也就是掛到內核中的文件系統類型鏈表上,來達到使文件系統能被內核識別的目的。反過來,內核也正是通過這條鏈表來跟蹤系統所支持的各種文件系統。

我們簡要分析一下注冊步驟:

struct?file_system_type?{

???????const?char *name;?????????????????????????????????????/*文件系統的名字*/

????????int?fs_flags;??????????????????????????????????????????????/*文件系統類型標志*/

/*下面的函數用來從磁盤中讀取超級塊*/

???????struct?super_block?* (*read_super)??(struct?file_system_type?*,?int,

???????????????????????????????????const?char *, void *);

???????struct?file_system_type?* next;???????????????????/*鏈表中下一個文件系統類型*/

???????struct?list_head?fs_supers;??????????????????????????/*超級塊對象鏈表*/

};

其中最重要的一項是read_super()函數,它用來從磁盤上讀取超級塊,并且當文件系統被裝載時,在內存中組裝超級塊對象。要實現一個文件系統首先需要實現的結構體便是file_system_type結構體。

注冊文件系統只能保證文件系統能被系統識別,但此刻文件系統尚不能使用,因為它還沒有被安裝到特定的安裝點上。所以在使用文件系統前必須將文件系統安裝到安裝點上。

文件系統被實際安裝時,將在安裝點創建一個vfsmount結構體。該結構體用來代表文件系統的實例——換句話說,代表一個安裝點。

vfsmount結構被定義在<linux/mount.h>中,下面是具體結構

―――――――――――――――――――――――――――――――――――――――

struct?vfsmount

{

???????struct?list_head?mnt_hash;????????????/*哈希表*/

???????struct?vfsmount?*mnt_parent;??????????????/*父文件系統*/

???????struct?dentry?*mnt_mountpoint;????/*安裝點的目錄項對象*/

???????struct?dentry?*mnt_root;?????????????????????/*該文件系統的根目錄項對象*/

???????struct?super_block?*mnt_sb;????????/*該文件系統的超級塊*/

???????struct?list_head?mnt_mounts;????????/*子文件系統鏈表*/

???????struct?list_head?mnt_child;????????????/*和父文件系統相關的子文件系統*/

???????atomic_t?mnt_count;????????????????????/*使用計數*/

???????int?mnt_flags;???????????????????????/*安裝標志*/

???????char?*mnt_devname;????????????/*設備文件名字*/

???????struct?list_head?mnt_list;??????????????/*描述符鏈表*/

};

――――――――――――――――――――――――――――――――――――――

文件系統如果僅僅注冊,那么還不能被用戶使用。要想使用它還必須將文件系統安裝到特定的安裝點后才能工作。下面我們接著介紹文件系統的安裝[7]過程。

安裝過程

用戶在用戶空間調用mount()命令——指定安裝點、安裝的設備、安裝類型等——安裝指定的文件系統到指定目錄。mount()系統調用在內核中的實現函數為sys_mount(),該函數調用的主要例程是do_mount(),它會取得安裝點的目錄項對象,然后調用do_add_mount()例程。

do_add_mount()函數主要做的是首先使用do_kern_mount()函數創建一個安裝點,再使用graft_tree()將安裝點作為葉子與根目錄樹掛接起來。

???整個安裝過程中最核心的函數就是do_kern_mount()了,為了創建一個新安裝點(vfsmount),該函數需要做以下幾件事情:

l????????檢查安裝設備的權利,只有root權限才有能力執行該操作。

l??????????Get_fs_type()在文件鏈表中取得相應文件系統類型(注冊時被填加到鏈表中)。

l?????????Alloc_vfsmnt()調用slab分配器為vfsmount結構體分配存儲空間,并把它的地址存放在mnt局部變量中。

l?????????初始化mnt->mnt_devname域

l?????????分配新的超級塊并初始化它。do_kern_mount( )檢查file_system_type描述符中的標志以決定如何進行如下操作:根據文件系統的標志位,選擇相應的方法讀取超級塊(比如對Ext2,romfs這類文件系統調用get_sb_dev();對于這種沒有實際設備的虛擬文件系統如?ramfs調用get_sb_nodev())——讀取超級塊最終要使用文件系統類型中的read_super方法。

????安裝過程做的最主要工作是創建安裝點對象,掛接給定文件系統到根文件系統的指定接點下,然后初始化超級快對象,從而獲得文件系統基本信息和相關操作方法(比如讀取系統中某個inode的方法)。

?

總而言之,注冊過程是告之內核給定文件系統存在于系統內;而安裝是請求內核對給定文件系統進行支持,使文件系統真正可用。

文件系統的讀寫

??? 要自己創建文件系統必須知道文件系統需要那些操作,各種操作的功能范圍,所以我們下面的內容就是分析Linux文件系統的文件讀寫過程,從中獲得文件系統的基本功能函數信息和作用范圍。

打開文件

在對文件進行寫前,必須先打開文件。打開文件的目的是為了使得目標文件能和當前進程關聯,同時需要將目標文件的索引節點從磁盤載入內存,并初始化。

open操作主要包含以下幾個工作要做(實際多數工作由sys_open()完成):

l??1?分配文件描述符號。

l??2?獲得新文件對象。

l??3?獲得目標文件的目錄項對象和其索引節點對象(主要通過open_namei()函數)——具體而言是通過調用索引節點對象(該索引節點或是安裝點或是當前目錄)的lookup方法找到目錄項對應的索引節點號ino,然后調用iget(sb,ino)從磁盤讀入相應索引節點并在內核中建立起相應的索引節點(inode)對象(其實還是通過調用sb->s_op->read_inode()超級塊提供的方法),最后還要使用d_add(dentry,inode)函數將目錄項對象與inode對象連接起來。

l??4?初始化目標文件對象的域,特別是把f_op域設置成索引節點中i_fop指向文件對象的操作表——以后對文件的所有操作將調用該表中的實際方法。

l???5?如果定義了文件操作的open方法(缺省),就調用它。

到此可以看到打開文件后,文件相關的“上下文”、索引節點、目錄對象等都已經生成就緒,下一步就是實際的文件讀寫操作了。

文件讀寫

用戶空間通過read/write系統調用進入內核執行文件操作,read操作通過sys_read內核函數完成相關讀操作,write通過sys_write內核函數完成相關寫操作。簡而言之,sys_read( )??和sys_write( )幾乎執行相同的步驟,請看下面:

l?1??調用fget(?)從fd獲取相應文件對象file,并把引用計數器file->f_count減1。

l?2??檢查file->f_mode中的標志是否允許請求訪問(讀或寫操作)。

l?3?調用locks_verify_area(?)檢查對要訪問的文件部分是否有強制鎖。

l?4??調用file->f_op->read?或file->f_op->write來傳送數據。兩個函數都返回實際傳送的字節數。

l?5??調用fput( )以減少引用計數器file->f_count的值

l?6?返回實際傳送的字節數。

搞清楚大體流程了吧?但別得意,現在僅僅看到的是文件讀寫的皮毛。因為這里的讀寫方法僅僅是VFS提供的抽象方法,具體文件系統的讀寫操作可不是表面這么簡單,接下來我們試試看能否用比較簡潔的方法把從這里開始到數據被寫入磁盤的復雜過程描述清楚。

現在我們要進入文件系統最復雜的部分——實際讀寫操作了。f_op->read/f_op->write兩個方法屬于實際文件系統的讀寫方法,但是對于基于磁盤的文件系統(必須有I/O操作),比如EXT2等,所使用的實際讀寫方法都是利用Linux系統已經提供的通用函數——generic_file_read/generic_file_write來完成的,這些通用函數的作用就是確定正被訪問的數據所在物理塊的位置,并激活塊設備驅動程序開始數據傳送,它們針對Unix風格的文件系統都能很好地完成功能?,所以沒必要自己再實現專用函數了。下面來分析這些通用函數。

先說讀方法:

第一 利用給定的文件偏移量(ppos)和讀寫字節數(count)計算出數據所在頁[8]的邏輯號(index)。

第二?然后開始傳送數據頁。

第三?更新文件指針,記錄時間戳等收尾工作。

其中最復雜的是第二部,首先內核會檢查數據是否已經駐存在頁高速緩存(page=?find_get_page(mmaping?,index),?其中mammping為頁高速緩存對象,index為邏輯頁號),如果在高速緩存中發現所需數據而且數據是有效的(通過檢查一些標志位,如,PG_uptodate),那么內核就可以從緩存中快速返回需要的頁;否則如果頁中的數據是無效的,那么內核將分配一個新頁面,然后將其加入到頁高速緩存中,隨即使用address_space對象的readpage方法(mapping->a_ops->readpage(file,page))激活相應的函數以便完成磁盤到頁的I/O數據傳送。之后[9]還要調用file_read_actor( )方法把頁中的數據拷貝到用戶態緩沖區,最好進行一些收尾等工作,如更新標志等。

?到此為止,我們才要開始涉及和系統I/O層打交道的工作,下面我們就來分析readpage函數具體如何激活磁盤到頁的I/O傳輸。

address_space對象的readpage方法存放的是函數地址,該函數激活從物理磁盤到頁高速緩存的I/O數據傳送。對于普通文件,該函數指針指向block_read_full_page( )函數的封裝函數。例如,REISEFS文件系統的readpage方法指向下列函數實現:

???int?reiserfs?_readpage(struct?file *file,?struct?page *page)

??? {

????return?block_read_full_page(page,?reiserfs?_get_block);

}

需要封裝函數是因為block_read_full_page( )函數接受的參數為待填充頁的頁描述符及有助于block_read_full_page()找到正確塊的函數get_block的地址。該函數依賴于具體文件系統,作用是把相對于文件開始位置的塊號轉換為相對于磁盤分區中塊位置的邏輯塊號。在這里它指向reiserfs?_get_block( )函數的地址。

block_read_full_page(?)函數的目的是對頁所在的緩沖區啟動頁I/O操作,具體將要完成這幾方面工作:

n?????????調用create_empty_buffers(??)為頁中包含的所有緩沖區[10]分配異步緩沖區首部

n?????????從頁所對應的文件偏移量(page->index域)導出頁中第一個塊的文件塊號

n?????????初始化緩沖區首部,最主要的工作是通過get_block函數進行磁盤尋址,找到緩沖區的邏輯塊號(相對于磁盤分區的開始而不是普通文件的開始)

n?????????對于頁中的每個緩沖區首部,對其調用submit_bh( )函數,指定操作類型為READ。

l?????????接下來的工作就該交給I/O傳輸層處理了,I/O層負責磁盤訪問請求調度和管理傳輸工作。我們簡要分析submit_bt()函數,該函數總體來說目的是向tq_disk任務隊列[11]提交請求,但它所做的工作頗多,下面就簡要分析該函數的行為:

u???????從b_blocknr(邏輯塊號)和b_size(塊大小)兩個域確定磁盤上第一個塊的扇區號,即b_rsector域的值

u???????調用generic_make_request()函數向低級別的驅動程序[12]發送請求,它接受的參數為緩沖區首部bh和操作類型rw。而該函數從低級驅動程描述符blk_dev[maj]中獲得設備驅動程序請求隊列的描述符,接著調用請求隊列描述符的make_request_fn方法

????make_request_fn方法是請求隊列定義的合并相臨請求、排序請求的主要執行函數。它將首先創建請求(實際上就是緩沖頭和磁盤扇區的映射關系);然后檢查請求隊列是否為空:

l?????????如果請求隊列為空,則把新的請求描述符插入其中,而且還要將請求隊列描述符插入tq_disk任務隊列,隨后再調度低級驅動程序策略例程的活動。

l?????????如果請求隊列不為空,則把新的請求描述符插入其中,試圖把它與其他已經排隊的請求進行組合(使用電梯調度算法)。

低級驅動程序的活動策略函數是request_fn方法。

策略例程通常在新請求插入到空列隊后被啟動。隨后隊列中的所有請求要依次進行處理,直到隊列為空才結束。

策略例程request_fn(定義在請求結構中)的執行過程如下:

u???????策略例程處理隊列中的第一個請求并設置塊設備控制器,使數據傳送完成后產生一個中斷。然后策略例程就終止。

u???????數據傳送完畢后塊設備控制器產生中斷,中斷處理程序就激活下半部分。這個下半部分的處理程序把這個請求從隊列中刪除(end_request( ))并重新執行策略例程來處理隊列中的下一個請求。

好了,讀操作說完了,是不是覺得不知所云呀,其實上面僅僅是抽取讀操作的骨架簡要講解,具體操作還要復雜得多,下面我們將上面的流程總結一下。

????粗略地分,讀操作依次需要經過:

l?????????用戶界面層——負責從用戶函數經過系統調用進入內核;

l?????????基本文件系統層——負責調用文件讀方法,從高速緩存中搜索數據頁,返回給用戶。

l?????????I/O調度層——負責對請求排隊,從而提高吞吐量。

l?????????I/O傳輸層——利用任務隊列異步操作設備控制器完成數據傳輸。

請看下圖4給出的邏輯流程。

?

?圖?4?讀操作流

?

?

寫操作和讀操作大體相同,不同之處主要在于寫頁面高速緩存時,稍微麻煩一些,因為寫操作不象讀操作那樣必須和用戶空間同步[13]執行,所以用戶寫操作更新了數據內容后往往先存儲在頁高速緩存中,然后等待頁回寫后臺例程bdflush和kupdate[14]等來完成寫入磁盤的工作。當然寫入請求處理還是要通過上面提到的submit_bh函數[15]進行I/O處理的。下面簡要介紹寫過程:

page?= __grab_cache_page(mapping,index,&cached_page,&lru_pvec);

status?=?a_ops->prepare_write(file,page,offset,offset+bytes);

page_fault?=?filemap_copy_from_user(page,offset,buf,bytes);

status?=?a_ops->commit_write(file,page,offset,offset+bytes);

首先,在頁高速緩存中搜索需要的頁,如果需要的頁不在高速緩存中,那么內核在高速緩存中新分配一空閑項;下一步,prepare_write()方法被調用,為頁分配異步緩沖區首部;接著數據被從用戶空間拷貝到了內核緩沖;最后通過commit_write()函數將對應的基礎緩沖區標記為臟,以便隨后它們被頁回寫例程寫回到磁盤。

好累呀,到此總算把文件讀寫過程順了一遍,大家明白了上述概念后,我們進入最后一部分:Romfs事例分析。

實例

文件系統實在是個龐雜的“怪物”,我很難編寫一個恰當的例子來演示文件系統的實現。開始我想寫一個純虛文件系統,但考慮到它幾乎沒有實用價值,而且更重要的是虛文件系統不涉及I/O操作,缺少現實文件系統中至關重要的部分,所以放棄了;后來想寫一個實際文件系統,但是那樣工程量太大,而且也不容易讓大家簡明扼要地理解文件系統的實現,所以也放棄了。最后我發現內核中提供的romfs文件系統是個非常理想的實例,它既有實際應用,結構也清晰明了,所以我們以romfs為實例分析文件系統的實現。

Linux文件系統實現要素

編寫新文件系統需要一些基本對象[16]。具體而言,創建文件系統需要建立“一個結構四個操作”:

n?????????文件系統類型結構(file_system_type)、

n?????????超級塊操作表(super_operations)、

n?????????索引節點操作表(inode_operations)、

n?????????頁高速緩存(address_space_operations)、

n?????????文件操作表(file_operations)。

對上述幾種結構的處理貫穿了文件系統的主要操作過程,理清晰這幾種結構之間的關系是編寫文件系統的基礎,下來我們具體分析這幾個結構和文件系統實現的要點。

?

?

?

你必須首先建立一個文件系統類型結構來“描述”文件系統,它含有文件系統的名稱、類型標志以及get_sb等操作。當你安裝文件系統時(mount)時,系統會解析“文件系統類型結構”,然后使用get_sb函數來建立超級節點“sb”,注意,對于基于塊的文件系統,如ext2、romfs等,需要從文件系統的宿主設備讀入超級塊以便在內存中建立對應的超級節點,如果是虛文件系統的話,則不是讀取宿主設備的信息(因為它沒有宿主設備),而是在現場創建一個超級節點,這項任務由get_sb完成。

超級節點可謂是一切文件操作的鼻祖,因為超級塊是我們尋找索引節點——索引節點對象包含了內核在操作文件或目錄時需要的全部信息——的唯一源頭,我們操作文件必然需要獲得其對應的索引節點(這點和建立超級節點一樣或從宿主設備讀取或現場建立),而獲取索引節點是通過超級塊操作表提供的read_node函數完成的,同樣操作索引節點的低層次任務,如創建一個索引節點、釋放一個索引節點,也都是通過超級塊操作表提供的有關函數完成的。所以超級塊操作表是我們第二個需要創建的數據類型。

除了派生或釋放索引節點等操作是由超級塊操作表中的函數完成外,索引節點還需要許多自操作函數,比如lookup搜索索引節點,為建立符號連接等,這些函數都包含在索引節點操作表中,因此我們下一個需要創建的數據類型就是索引節點操作表。

為了提高文件系統的讀寫效率,Linux內核設計了I/O緩存機制。所有的數據無論出入都會經過系統管理的高速緩存——對于非基于塊的文件系統則可跳過該機制。出于操作數據的目的,頁高速緩存同樣提供了一個函數操作表,其中包含有readpage()、writepage()等函數,負責操作高速緩存中的頁讀寫。

?????文件系統最終和用戶交互還需要實現文件操作表,該表中包含有關用戶讀寫文件、打開、關閉、映射等用戶接口。

?????對于基于塊的文件系統實現的一般方式而言,都離不開以上5種數據結構。但根據文件系統的特點(如有的文件系統只可讀、有的沒有目錄),并非要實現操作表中的全部函數,換句話說,你只需要實現部分函數,而且系統已經存在很多函數現成的通用方法,因此留給你做的事情其實不多。

Romfs文件系統是什么

Romfs是基于塊的只讀文件系統,它使用塊(或扇區)訪問存儲設備驅動(比如磁盤,CD,ROM盤)。由于它小型、輕量,所以常常用在嵌入系統和系統引導時。

Romfs是種很簡單的文件系統,它的文件布局和Ext2等文件系統相比要簡單得多。它比ext2文件系統要求更少的空間。空間的節約來自于兩個方面,首先內核支持romfs文件系統比支持ext2文件系統需要更少的代碼,其次romfs文件系統相對簡單,在建立文件系統超級塊(superblock)需要更少的存儲空間。Romfs文件系統不支持動態擦寫保存,對于系統需要動態保存的數據采用虛擬ram盤的方法進行處理(ram盤將采用ext2文件系統)。

下面我們來分析一下它的實現方法,為讀者勾勒出編寫新文件系統的思路和步驟。希望能為大家自己設計文件系統起到拋磚引玉的效果。

Romfs文件系統布局與文件結構

文件系統簡單理解就是數據的分層存儲結構,數據由文件組織,而文件又由文件系統安排存儲形式,所以首先必須設計信息和文件組織形式——也就是這里所說的文件布局。

??????

在Linux內核源代碼中的Document/fs/romfs中介紹了romfs文件系統的布局和文件結構。下面我們簡要說明一下它們。

?

?

??????????????????????????????????????????圖?5?romfs文件系統布局

上圖是romfs布局圖,可以看到文件系統中每部分信息都是16位對的,也就是說存儲的偏移量最后4位必須為0,這樣做是為了提高訪問速度。如果信息不足時,需要填充0以保證所有信息的開始位置都為16為對齊[17]。

???文件系統的開始8個字節存儲文件系統的ASCII形式的名稱,比如“romfs”;接著4個字節記錄文件大小;然后的4個字節存儲的是文件系統開始處512字節的檢驗和;接下來是卷名;最后是第一個文件的文件頭,從這里開始依次存儲的信息就是文件本身了。

??Romfs的文件結構也非常簡單,我們看下圖

?

?

?

具體需要實現的對象

???Romfs文件系統定義針對文件系統布局和文件結構定義了一個磁盤超級塊結構和磁盤inode(對應于文件)結構:

struct romfs_super_block {
 ??? ?????????__u32 word0;
 ??????? ?????__u32 word1;
 ??????? ?????__u32 size;
 ?????????????__u32 checksum;
char name[0];????????? 
?};
? struct romfs_inode {
????????? __u32 next;?????????? 
?????????__u32 spec;
????????? __u32 size;
????????? __u32 checksum;
????????? char name[0];
? };

上述兩種結構分別描述了文件系統結構與文件結構,它們將在內核裝配超級塊對象和索引節點對象時被使用。

???Romfs文件系統首先要定義的對象是文件系統類型romfs_fs_type。定義該對象同時還要定義讀取超級塊的函數romfs_read_super。

ramfs_read_super()作用是從磁盤讀取磁盤超級塊給超級塊對象,具體行為如下

1 裝配超級塊。

??1.1 初始化超級塊對象某些域。

1.2 從設備中讀取磁盤第0塊到內存到內存?bread(dev,0,ROMBSIZE),其中dev是文件系統安裝時指定的設備,0指設備的首塊,也就是磁盤超級塊,ROMBSIZE是讀取的大小。

??1.3 檢驗磁盤超級塊中的校驗和

??1.4 繼續初始化超級塊對象某些域

2 給超級塊對象的操作表賦值(s->s_op = &romfs_ops)

3 為根目錄分配目錄項 s->s_root = d_alloc_root(iget(s,sz), sz為文件系統開始偏移。

???? 超級塊操作表中romfs文件系統實現了兩個函數
static struct super_operations romfs_ops = {
??????? read_inode:???? romfs_read_inode,
??????? statfs:???????? romfs_statfs,
};

第一個函數read_inode(inode)是用磁盤上的數據填充參數指定的索引節點對象的域;索引節點對象的i_ino域標識從磁盤上要讀取的具體文件系統的索引節點。

1?根據inode參數尋找對應的索引節點。

2?初始化索引節點的某些域

3?根據文件的訪問權限(類別)設置索引節點的相應操作表

? 31 如果是目錄文件則將索引節點表設為i->i_op = &romfs_dir_inode_operations;文件操作表設置為->i_fop = &romfs_dir_operations; 如果索引節點對應目錄的話,那么需要的操作僅僅會是lookup操作(因為romfs是個功能很有限的文件系統);對于文件操作表中的兩個方法一個為read,另一個為readdir。前者利用通用函數generic_read_dir,返回用戶錯誤消息。后者是針對readdir/getdents等系統調用的實現返回目錄中文件的函數
? 3.2 如果是常規文件,則將文件操作表設置為i->i_fop = &generic_ro_fops; 
將頁高訴緩存表設置為i->i_data.a_ops = &romfs_aops;由于romfs是只讀文件系統,它在對正規文件操作時不需要索引節點操作,如mknod,link等,因此不用給出索引節點操作表。
對常規文件的操作也只需要使用內核提供的通用函數表struct generic_ro_fops ,它包含基本的三種常規文件操作:
 llseek:???????? generic_file_llseek,
 ????read:?????????? generic_file_read,
mmap:?????????? generic_file_mmap,
?? 利用這幾種通用函數,完全能夠滿足romfs文件系統的的文件操作要求,具體函數請自己閱讀源代碼。
回憶前面我們提到過的頁高速緩存,顯然常規文件訪問需要經過它,因此有必要實現頁高訴緩存操作。因為只需要讀文件,所以只用實現romfs_readpage函數,這里readpage函數使用輔助函數romfs_copyfrom完成將數據從設備讀入頁高速緩存,該函數根據文件格式從設備讀取需要的數據。設備讀取操作需要使用bread塊I/O例程,它的作用是從設備讀取指定的塊[18]
? 3.3 如果是連接文件,則將索引節點操作表設置為:i->i_op=&page_symlink_inode_operations;
將頁高速緩存操作表設置為:
i->i_data.a_ops = &romfs_aops;
符號連接文件需要使用通用符號連接操作page_symlink_inode_operations實現,同時也需要使用頁高速緩存方法。 
??3.4 如果是套接字或管道則進行特殊文件初始化操作init_special_inode(i, ino, nextfh);
到此,我們已經遍歷romfs文件系統使用的幾種對象結構:romfs_super_block、romfs_inode、romfs_fs_type、super_operations romfs_opsaddress_space_operations romfs_aops file_operations romfs_dir_operationsinode_operations romfs_dir_inode_operations 。實現上述對象是設計一個新文件系統的最低要求。
?

最后要說明的是,為了使得romfs文件系統作為模塊掛載,需要實現static?int?__init?init_romfs_fs(void)

{

????return?register_filesystem(&romfs_fs_type);

}

static?void __exit?exit_romfs_fs(void)

{

????unregister_filesystem(&romfs_fs_type);

}

兩個在安裝romfs文件系統模塊時使用的例程。

安裝和卸載

module_init(init_romfs_fs)

module_exit(exit_romfs_fs)

????到此,romfs文件系統的關鍵結構都已介紹完畢,至于細節還是需要讀者仔細推敲。Romfs是最簡單的基于塊的只讀文件系統,而且沒有訪問控制等功能。所以很多訪問權限以及寫操作相關的方法都不必去實現。

??????使用romfs首先需要genromfs來制作romfs文件系統鏡像(類似于使用mke2fs格式化文件系統),然后安裝文件系統鏡像?mount –t?romfs?********。?Romfs可以在編譯內核時制定編譯成模塊或編入內核,如果是模塊則需要首先加載它。

?

??? 小結:實現文件系統必須向上要清楚文件系統和系統調用的關系,向下要了解文件系統和I/O調度、設備驅動等的聯系。另外還必須了解關于緩存、進程、磁盤格式等概念。在這里并沒有對這些問題進行深入分析,僅僅提供給大家一個文件系統全景圖,希望能對自己設計文件系統有所幫助。文件系統內容龐大、復雜,許多問題我也不確定,有文件系統經驗的朋友希望能夠廣泛交流。



[1]?請參見??OPERATION SYSTEMS INTERNALS AND DESIGN PRINCIPLES?一書第12章

[2]?扇區是磁盤的最小尋址單元,而文件塊是內核操作文件的最小單位,一個塊可以包含一個或數個扇區。這些磁盤塊被讀入內存后即刻被存入緩沖中,同樣,文件塊被寫出也要通過緩沖。

[3]?如果文件按記錄形式組織,那么數據在成為文件塊前,還要經過記錄形式的階段。

[4]?摘自?Linux?內核開發?中第?11?章中文件系統抽象層一節

[5]?請看Linux?內核開發?一書第11章

[6]?在2.6內核以后,緩沖頭的作用并不象以前那么重要了。因為2.6中緩沖頭僅僅作為內核中的I/O操作單元,而在2.6以前緩沖頭不但是磁盤塊到物理內存的映射,而且還是所有塊I/O操作的容器。

[7]?這里安裝的文件系統屬于非根文件系統的安裝方法。根文件系統安裝方法有所區別,請查看相關資料。

[8]?無論讀文件或寫文件,文件中的數據都是必須經過內存中的頁高速緩存做中間存儲才能夠被使用。高速緩存由一個叫做address_space的特殊數據結構表示,其中含有對頁高速緩存宿主(address_space->host)的操作表。

[9]?這期間要要處理一些預讀,以此提高未來訪問的速度。

[10]??緩沖與相應的塊一一對應,它的作用相當于磁盤塊在內存中的表示。

[11]??tq_disk是專門負責磁盤請求的任務隊列,任務隊列是用來推后異步執行的一種機制。2.6內核中已經用工作隊列代替了任務隊列。

[12]??塊設備驅動程序可以劃分為兩部分:低級驅動程序(blk_dev_struct)和高級設備驅動(block_device)。低級設備驅動程序作用是記錄每個高級驅動程序送來的請求組成的隊列。

[13]?文件讀取操作必須同步進行,在讀取的數據返回前,工作無法繼續進行。而且如果結果在30秒內回不來,則用戶必將無法忍受,所以讀操作執行緊迫。而對于寫操作,則可以異步執行,因為寫入操作一般不會影響下一步的執行,所以緊迫性也低。

[14]?bdflush和kupdate?分別是當空閑內存過低時釋放臟頁和當臟緩沖區在內存中存在時間過長時刷新磁盤的。而在2.6內核中,這兩個函數的功能已經被pdflush統一完成。

[15]?實際上是從block_read_full_page( )函數中調用submit_bh()函數的。

[16]?對象指的是內存中的結構體實例,而不是物理上的存儲結構。

[17]?創建romfs文件系統可使用genromfs格式化工具。

[18]?索引節點結構描述了從物理塊(塊設備上的存取單位,是每次I/O操作最小傳輸的數據大小)到邏輯塊(文件實際操作基本單元)的映射關系。

原文鏈接:http://www.kerneltravel.net/journal/vii/%E5%A6%82%E4%BD%95%E5%AE%9E%E7%8E%B0%E4%B8%80%E4%B8%AA%E6%96%87%E4%BB%B6%E7%B3%BB%E7%BB%9F.htm

轉載于:https://www.cnblogs.com/earendil/p/5009102.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/256340.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/256340.shtml
英文地址,請注明出處:http://en.pswp.cn/news/256340.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【tenserflow】——數據類型以及常用屬性

目錄 一、什么是Tensor&#xff1f; 二、Tensorflow常見數據類型 三、Tensorflow常見屬性device\cpu\gpu\ndim\shape\rank等 1、創建一個tensor 1&#xff09;tf.constant() 2)tf.Variable() 2、判斷一個變量是否為tensor張量 3、生成不同設備&#xff08;cpu,gpu&#x…

C# 事件詳解附實例分析

一、定義 事件是兩個對象間發布消息和響應后處理消息的過程&#xff0c;通過委托類型來實現的。 事件的機制被稱為發布-訂閱機制&#xff0c;其算法過程為&#xff1a;首先定義一個委托類型&#xff0c;然后在發布者類中聲明一個event事件&#xff0c;同時此類中還有一個用來觸…

網頁開發瀏覽器兼容性問題

1、在ie6下的雙margin問題 在ie6下&#xff0c;設置了float的元素&#xff0c;以float:left為例&#xff0c;如圖所示。會出現第一個浮動元素&#xff0c;即相對于父級元素浮動的&#xff0c;會出現雙倍margin的問題。 注意僅僅是相對于父級元素浮動的&#xff0c;即第一個會出…

【tensorflow】——創建tensor的方法

目錄 1、tf.constant() 2、tf.Variable() 3、tf.zeros():用0去填充指定形狀的數組 4、tf.convert_to_tensor(a,dtypetf.int32) 5、tf.ones():用1去填充指定形狀的數組 6、tf.fill():用指定的元素去填充指定形狀的數組 7、隨機化初始化進行創建 1&#xff09;normal正態分…

Halcon —— 圖像像素類型與轉換

圖像類型 就目前工業領域主流的圖像處理工具halcon來講&#xff0c;有以下幾種圖像類型&#xff1a;‘byte’, ‘complex’, ‘cyclic’, ‘direction’, ‘int1’, ‘int2’, ‘int4’, ‘int8’, ‘real’, ‘uint2’&#xff0c;具體含義如下圖所示。 ‘byte’ 每像素1字節…

軟件方法

核心工作流業務建模&#xff08;組織建模&#xff09;&#xff1a;描述組織內部各個系統如何協作&#xff0c;使得組織可以為其他的組織提供有價值的服務&#xff0c;新系統只不過是組織為了對外提供更好的服務&#xff0c;對自己的內部重新設計而購買的一個零件。需求&#xf…

修改vim中的tab為4個空格

記錄一下&#xff0c;避免用時還得搜........ 1、臨時修改 在vi中&#xff0c;set tabstop4 或 set ts4  2、永久修改 vi --version 查看要修改的文件如果是vim的話&#xff0c;修改~/.vimrc如果是vi&#xff0c;修改~/.exrc加上&#xff1a;set tabstop4set nu //顯示行號set…

Halcon例程詳解(基于卡尺工具的匹配測量方法) —— measure_stamping_part.hdev

前言 1卡尺工具介紹 Halcon中的Metrology方法即為卡尺工具&#xff0c;可用來擬合線&#xff0c;圓&#xff0c;這種方法對于目標比背景很明顯的圖像尺寸測量是很方便的&#xff0c;不需要用blob進行邊緣提取等&#xff0c;但缺點也很明顯&#xff0c;需要目標的相對位置基本…

【TensorFlow】——不同shape的tensor在神經網絡中的應用(scalar,vector,matrix)

目錄 ? 1、scalar——標量 1&#xff09;在神經網絡中存在的場景 2&#xff09;one_hot編碼 3&#xff09;舉例應用 2、vector——向量 ? 3、matrixs——矩陣 4、dim3的tensor 5、dim4的tensor 6、dim5的tensor 本文主要的目的是讓初學者對tensor的各種形式的使用場…

404頁面 3秒后跳到首頁 實現

---恢復內容開始--- 當我們訪問一個頁面不存在的時候&#xff0c;就會跳到404頁面 一般網站都在在404頁面中做一個處理&#xff0c; 就是當用戶3秒種內還沒有任何操作的話&#xff0c;就會自動跳轉到其它頁面 技術實現有兩種方法 1. 在404頁面中的header間加上 <meta http-e…

Java - I/O

File類 java.io操作文件和目錄&#xff0c;與平臺無關。具體的常用實例方法&#xff1a; File file new File("."); // 以當前路徑創建名為 "." 的 File 對象 ? 文件目錄信息函數 ? ? - ? String getName/Path/Parent()&#xff1a; 文件名/路徑…

Halcon —— 邊緣檢測算子詳解

一、算子介紹 1.1 種類 halcon內常用的邊緣檢測算子包括如下幾種&#xff1a; 1.edges_image: 提取2D 圖像邊緣 2.edges_sub_pix&#xff1a;提取2D圖像亞像素邊緣 3.edges_object_model_3d &#xff1a;提取3D圖像邊緣 4.edges_color和edges_color_sub_pix&#xff1a;提取彩…

【TensorFlow】——索引與切片

目錄 1、利用index進行索引 2、利用“&#xff1a;”和“...”進行索引與切片 3、tf.gather&#xff08;&#xff09;——對一個維度進行亂序索引 優勢&#xff1a; 缺點&#xff1a; 例子 4、tf.gather_nd()——同時對多個維度進行索引 5、tf.boolean_mask()——通過布…

華碩(ASUS)X554LP筆記本一開機就進入aptio setup utility 問題的解決

某次因大意一直未插電&#xff0c;華碩&#xff08;ASUS&#xff09;X554LP筆記本后來沒電關機。后來每次一開機就進入aptio setup utility界面&#xff0c;按F9調入默認配置&#xff0c;F10保存后退出&#xff0c;重啟仍然進入aptio setup utility。 網上查了一下&#xff0c;…

redis和memcached緩存

memcached memcache開源的&#xff0c;高性能&#xff0c;高并發分布式內存緩存系統,天生支持集群 memcached下載地址&#xff1a; http://memcached.org/downloads python實現memcached緩存 pip3 install python-memcached import memcache aamemcache.Client(["10.0.0.2…

C# —— 進程與線程的理解

定義 進程 進程&#xff08;Process&#xff09;是Windows系統中的一個基本概念&#xff0c;它包含著一個運行程序所需要的資源。一個正在運行的應用程序在操作系統中被視為一個進程&#xff0c;進程可以包括一個或多個線程。 程序是在特定操作系統上的可執行文件&#xff0c…

git安裝和初步使用

基本參照以下鏈接&#xff1a; http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000/00137583770360579bc4b458f044ce7afed3df579123eca000 注意&#xff1a; &#xff08;1&#xff09;每臺電腦對于github來說都有對應的ssh密鑰&#xff0c;…

【TensorFlow】——broadcast_to(在不復制內存的情況下自動擴張tensor)

目錄 作用&#xff1a; 內在的思路 優點 什么時候可以broadcast ? tf.boradcast_to .VS tf.tile 作用&#xff1a; 在不會實際意義上復制數據的情況下進行tensor的維度和形狀的擴張&#xff0c;使得兩個tensor維度和形狀一致 對兩個維度不一致的tensor進行加減前進行br…

20145212 《信息安全系統設計基礎》第2周學習總結

20145212 《信息安全系統設計基礎》第2周學習總結 教材學習內容總結 Vim基本操作 1.使用vim命令進入vim界面vim后面加上你要打開的已存在的文件名或者不存在&#xff08;則作為新建文件&#xff09;的文件名。 打開Xfce終端&#xff0c;輸入以下命令$ vim practice_1.txt 直接使…

Opencv—— 擬合直線

概念 最小二乘法是勒讓德( A. M. Legendre)于1805年在其著作《計算慧星軌道的新方法》中提出的。 最小二乘法就是通過最小化誤差的平方和&#xff0c;使得擬合對象無限接近目標對象。在圖像處理中主要用于擬合線&#xff0c;通過求采樣點距離誤差最小的線&#xff0c;可以是直…