Linux基礎io知識

理解 "文件"

狹義理解

文件在磁盤里
磁盤是永久性存儲介質,因此文件在磁盤上的存儲是永久性的
磁盤是外設(即是輸出設備也是輸入設備)
磁盤上的文件 本質是對文件的所有操作,都是對外設的輸入和輸出 簡稱 IO

廣義理解

Linux 下一切皆文件(鍵盤、顯示器、網卡、磁盤…… 這些都是抽象化的過程)

文件操作的歸類認知

對于 0KB 的空文件是占用磁盤空間的
文件是文件屬性(元數據)和文件內容的集合(文件 = 屬性(元數據)+ 內容)
所有的文件操作本質是文件內容操作和文件屬性操作

系統角度

對文件的操作本質是進程對文件的操作
磁盤的管理者是操作系統
文件的讀寫本質不是通過 C 語言 / C++ 的庫函數來操作的(這些庫函數只是為用戶提供方便),而是通過文件相關的系統調用接口來實現的

C語言的fopen,fwrite,fread是庫函數并不是系統調用,這三個接口都是封裝了操作系統底層的系統調用

回顧C文件接口

打開文件

#include <stdio.h>
int main()
{FILE *fp = fopen("myfile", "w");if(!fp){printf("fopen error!\n");}while(1);fclose(fp);return 0;
}

寫文件

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "w");if(!fp){printf("fopen error!\n");}const char *msg = "hello bit!\n";int count = 5;while(count--){fwrite(msg, strlen(msg), 1, fp);}fclose(fp);return 0;
}

讀文件

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "r");if(!fp){printf("fopen error!\n");return 1;}char buf[1024];const char *msg = "hello bit!\n";while(1){//注意返回值和參數,此處有坑,仔細查看man?冊關于該函數的說明 ssize_t s = fread(buf, 1, strlen(msg), fp);if(s > 0){buf[s] = 0;printf("%s", buf);}if(feof(fp)){break;}}fclose(fp);return 0;
}

管理文件

一個進程可以打開多個文件,所以系統中存在大量被打開或正要關閉的文件,所以操作系統需要管理文件,先描述,再組織!!!

操作系統通過一個結構體struct file來管理文件,在文件被打開的時候就會創建一個struct file,其中存放著文件的各種屬性,結構體之間互相鏈接形成鏈表,所以操作系統對文件的管理就轉換成了對鏈表的管理

task_struct中不止存在頁表,還存在著一個文件描述符表struct files_struct,其中存放著一個? struct file數組,數組的每一個的下標都鏈接著一個文件的地址,系統就通過數組來管理每一個進程打開的文件。

相對應一個文件也可以被多個進程打開,那么關閉文件是否會對進程造成影響,畢竟進程是具有獨立性的,而struct file使用和智能指針類似的做法,就是引用計數,只有計數為0的時候才會真正關閉文件

文件的內容會加載到文件緩沖區,文件的屬性會加載到struct file

系統文件 I/O

初識標志位

打開文件的方式不僅僅是 fopen,ifstream 等流式,語言層的方案,其實系統才是打開文件最底層的方案。不過,在學習系統文件 IO 之前,先要了解下如何給函數傳遞標志位,該方法在系統文件 IO 接口中會使用到:

#include <stdio.h>
#define ONE 0001 //0000 0001
#define TWO 0002 //0000 0010
#define THREE 0004 //0000 0100
void func(int flags) {if (flags & ONE) printf("flags has ONE! ");if (flags & TWO) printf("flags has TWO! ");if (flags & THREE) printf("flags has THREE! ");printf("\n");
}
int main() {func(ONE);func(THREE);func(ONE | TWO);func(ONE | THREE | TWO);return 0;
}

通過這個代碼能夠了解標志位的作用

狀態表示:每個標志位都能獨立地表示一種狀態。在這個例子中,ONE、TWO 和 THREE 分別代表不同的狀態。

狀態組合:通過按位或運算符 | 可以把多個標志位組合起來,以此表示多種狀態的組合。

狀態檢查:借助按位與運算符 & 能夠檢查某個標志位是否被設置,從而依據不同的狀態執行不同的操作。

系統調用讀文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{int fd = open("myfile", O_RDONLY);if(fd < 0){perror("open");return 1;}const char *msg = "hello bit!\n";char buf[1024];while(1){ssize_t s = read(fd, buf, strlen(msg));//類?write if(s > 0){printf("%s", buf);}else{break;}}close(fd);return 0;
}

系統調用寫文件

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{umask(0);int fd = open("myfile", O_WRONLY|O_CREAT, 0644);if(fd < 0){perror("open");return 1;}int count = 5;const char *msg = "hello bit!\n";int len = strlen(msg);while(count--){write(fd, msg, len);//fd: 后?講, msg:緩沖區?地址, len: 本次讀取,期望寫?多少個字節的數據。 返回值:實際寫了多少字節數據 }close(fd);return 0;
}

接口認識

open
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

第一個參數是文件,第二個參數是標志位?,第三個參數表示新創建文件的權限,如果新創建的文件不帶mode會造成新創建的文件權限位亂碼

通過使用標志位用偶有效減少傳參的個數

open標志位介紹

O_RDONLY:以只讀模式打開文件。文件只能被讀取,不能進行寫入操作
O_WRONLY:以只寫模式打開文件。文件只能被寫入,不能進行讀取操作
O_RDWR:以讀寫模式打開文件。文件既可以被讀取,也可以被寫入
O_CREAT:如果文件不存在,則創建該文件。使用此標志位時,需要提供第三個參數 mode 來指定新文件的權限。
O_EXCL:和 O_CREAT 一起使用時,如果文件已經存在,則 open 調用會失敗并返回 -1,同時將 errno 設置為 EEXIST。這可用于確保文件是新創建的。
O_TRUNC:如果文件存在且以可寫模式打開,則將文件長度截斷為零,即清空文件內容
O_APPEND:以追加模式打開文件。每次寫操作都會將數據追加到文件末尾,而不是覆蓋原有內容
O_NONBLOCK:以非阻塞模式打開文件。在進行讀寫操作時,如果沒有數據可讀或無法立即寫入數據,函數不會阻塞,而是立即返回 -1,同時將 errno 設置為 EAGAIN 或 EWOULDBLOCK。這在處理多個文件描述符或需要異步操作時非常有用

而標志位的組合使用|

write
ssize_t write(int fd, const void *buf, size_t count);

第一個參數是文件操作符,第二個參數是寫入文件的內容,第三個參數是需要寫入的字節數

read?
ssize_t read(int fd, void *buf, size_t count);

第一個參數是文件操作符,第二個參數是存放讀到的數據,第三個參數是讀入的字節數

文件操作符

write,read的第一個參數都是fd,fd就是文件操作符

操作系統只認fd,那么fd是什么呢?在文件描述符表中存在struct file數組,fd就是數組的下標。

當我們打開多個文件的時候,查看他們的fd,會發現fd是從3開始的。這是由于數組中默認的0,1,2號下標分別是標準輸入,標準輸出,標準錯誤。

fd的數據是從小到大的,只要前面有多余的位置,系統就會將文件分配到靠前的下標

open的時候就會找到進程的文件描述符表中的數組,找到一個未被使用過的位置,將struct file的地址填入其中,fd就是數組的下標

read就是用fd找到數組的下標,訪問對應文件的地址,從文件緩沖區中將數據拷貝到buffer,文件緩沖區的數據是由磁盤中文件的內容預加載到緩沖區中。

重定向

Linux中存在函數dup2,用于復制文件描述符

int dup2(int oldfd, int newfd);

oldfd:需要被復制的源文件描述符。
newfd:復制到的目標文件描述符。如果newfd已經打開,會先將其關閉。

使用dup2會將newfd位置的文件覆蓋到oldfd上

可以通過dup2來替換標準輸入,標準輸出來實現重定向操作。

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int main()
{int fd = open("test.txt", O_RDWR);dup2(fd,1);close(fd);printf("fd: %d\n", fd);return 0;
}

當我們將文件夾中的test.txt替換掉標準輸出,此時使用printf,?則不會將內容輸出到顯示器上,而是將內容輸出到test.txt中

操作系統只認識fd,printf就是為了往stdout中輸出內容,但是stdout被關閉,此時fd為1的文件是log.txt,printf輸出的內容就會輸出到log.txt中

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<unistd.h>
int main()
{int fd = open("test.txt", O_RDWR);dup2(fd,0);close(fd);char s[100];scanf("%s",s);printf("%s\n",s);return 0;
}

將test.txt文件替換標準輸入文件,此時scanf一般是從鍵盤上讀取數據,此時就只能從test.txt中讀取數據,將test.txt的數據填寫到s中。

操作系統只認fd,scanf就是從鍵盤中讀取數據,但是標準輸入被關閉,此時fd為0的文件是test.txt,此時scanf就會從?test.txt中讀取數據

通過上面兩端代碼可以大致了解重定向的原理,就是將文件進行替換,從而將一下本該輸出到顯示屏中的數據輸出到其他文件中,將從鍵盤中讀取的數據轉換成從其他文件中讀取數據?

正確的重定向操作方法?

例如存在一個文件:log.txt,需要將原本輸出到顯示屏的數據輸出到log.txt中

ls -l 1 > log.txt

將原本輸出到1號文件的內容輸出到log.txt中?,而1號文件就是stdout

#include<cstdio>
#include<iostream>
using namespace std;
int main()
{cout<<"hellocout"<<endl;cerr<<"hellocerr"<<endl;return 0;
}

當我們使用這段代碼重定向到一個文件時,cerr的內容并不會重定向到文件,而是輸出在顯示屏上,這是由于cerr對應的文件時stderr,它的fd為2,而默認的重定向只有1

所以可以通過將1重定向到log.txt,2追加重定向到log.txt

./a 1 > log.txt 2 >> log.txt

此時cout和cerr的內容就都輸出到log.txt中了?

也可以將標準錯入重定向到標準輸出的目標位置,&1?表示「當前標準輸出的目標位置」

./a 1 > log.txt 2>&1

效果與第一種一樣?

理解“一切皆文件”

首先,在 Windows 中是文件的東西,它們在 Linux 中也是文件;其次一些在 Windows 中不是文件的東西,比如進程、磁盤、顯示器、鍵盤這樣硬件設備也被抽象成了文件,你可以使用訪問文件的方法訪問它們獲得信息;甚至管道,也是文件;將來我們要學習網絡編程中的 socket(套接字)這樣的東西,使用的接口跟文件接口也是一致的。

這樣做最明顯的好處是,開發者僅需要使用一套 API 和開發工具,即可調取 Linux 系統中絕大部分的資源。舉個簡單的例子,Linux 中幾乎所有讀(讀文件,讀系統狀態,讀 PIPE)的操作都可以用 read 函數來進行;幾乎所有更改(更改文件,更改系統參數,寫 PIPE)的操作都可以用 write 函數來進行。

在操作系統中,每個外設都有一套屬于自己的讀寫操作。操作系統中的struct file的內容中存在函數指針,他們會指向對應外設的讀寫操作,而每個函數指針的命名,參數,都一樣

一切皆文件,是進程認為一切皆文件,進程中存儲著文件描述符表,文件描述符表指向struct file,從硬件角度上來看,struct file的函數指針指向硬件的讀寫操作,所以管理好文件也就能管理好硬件,進程無需接觸到硬件,只需接觸到struct file即可

上圖中的外設,每個設備都可以有自己的 read、write,但一定是對應著不同的操作方法!!但通過struct file 下 file_operation 中的各種函數回調,讓我們開發者只用 file 便可調取 Linux 系統中絕大部分的資源!!這便是 “linux 下一切皆文件” 的核心理解。?

緩沖區?

什么是緩沖區

緩沖區就如同日常中的快遞站,可以幫助我們在空閑時間再去拿快遞,而非在快遞送到家門口的時候無論在做任何事情都要回去拿快遞,這樣也嚴重影響了快遞員的效率

緩沖區是內存空間的一部分。也就是說,在內存空間中預留了一定的存儲空間,這些存儲空間用來緩沖輸入或輸出的數據,這部分預留的空間就叫做緩沖區。緩沖區根據其對應的是輸入設備還是輸出設備,分為輸入緩沖區和輸出緩沖區。

可以查看Linux中的FILE結構體

它的指針就指向了緩沖區的位置

為什么要引入緩沖區機制

系統調用也是有成本的,系統調用是需要操作系統進行操作的,而操作系統平時需要進行大量的資源管理,如果沒有緩沖區,當我們進行文件寫入時,操作系統無論做什么事情都需要停下來進行寫入操作

讀寫文件時,如果不會開辟對文件操作的緩沖區,直接通過系統調用對磁盤進行操作 (讀、寫等),那么每次對文件進行一次讀寫操作時,都需要使用讀寫系統調用處理此操作,即需要執行一次系統調用,執行一次系統調用將涉及到 CPU 狀態的切換,即從用戶空間切換到內核空間,實現進程上下文的切換,這將損耗一定的 CPU 時間,頻繁的磁盤訪問對程序的執行效率造成很大的影響。

為了減少使用系統調用的次數,提高效率,我們就可以采用緩沖機制。比如我們從磁盤里取信息,可以在磁盤文件進行操作時,可以一次從文件中讀出大量的數據到緩沖區中,以后對這部分的訪問就不需要再使用系統調用了,等緩沖區的數據取完后再去磁盤中讀取,這樣就可以減少磁盤的讀寫次數,再加上計算機對緩沖區的操作 快于對磁盤的操作,故應用緩沖區可 大提高計算機的運行速度。

又如,我們使用打印機打印文檔,由于打印機的打印速度相對較慢,我們先把文檔輸出到打印機相應的緩沖區,打印機再自行逐步打印,這時我們的 CPU 可以處理別的事情。可以看出,緩沖區就是一塊內存區,它用在輸入輸出設備和 CPU 之間,用來緩存數據。它使得低速的輸入輸出設備和高速的 CPU 能夠協調工作,避免低速的輸入輸出設備占用 CPU,解放出 CPU,使其能夠高效率工作。

緩沖類型

標準 I/O 提供了 3 種類型的緩沖區。

?全緩沖區:這種緩沖方式要求填滿整個緩沖區后才進行 I/O 系統調用操作。對于磁盤文件的操作通常使用全緩沖的方式訪問。

?行緩沖區:在行緩沖情況下,當在輸入和輸出中遇到換行符時,標準 I/O 庫函數將會執行系統調用操作。當所操作的流涉及一個終端時(例如標準輸入和標準輸出),使用行緩沖方式。因為標準 I/O 庫每行的緩沖區長度是固定的,所以只要填滿了緩沖區,即使還沒有遇到換行符,也會執行 I/O 系統調用操作,默認行緩沖區的大小為 1024。

?無緩沖區:無緩沖區是指標準 I/O 庫不對字符進行緩存,直接調用系統調用。標準出錯流 stderr 通常是不帶緩沖區的,這使得出錯信息能夠盡快地顯示出來。

緩沖區的工作

當使用printf/fprintf/fwrite等等的庫函數進行文件操作的時候,數據并非直接寫入到文件內核緩沖區中,c標準庫中存在一個庫緩沖區,使用庫函數進行操作的時候,數據會先占時寫入到庫緩沖區中,直到某種條件的觸發,會將fopen返回值中的fd找出來,通過fd進行系統調用,將庫緩沖區的內容寫到文件內核緩沖區中。

刷新緩沖區的條件

1.強制刷新

2.進程退出

3.刷新條件滿足->(全緩沖,行緩沖,無緩沖)

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

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

相關文章

視頻編解碼學習三之顯示器續

一、現在主流的顯示器是LCD顯示器嗎&#xff1f; 是的&#xff0c;現在主流的顯示器仍然是 LCD&#xff08;液晶顯示器&#xff0c;Liquid Crystal Display&#xff09;&#xff0c;但它已經細分為多種技術類型&#xff0c;并和其他顯示技術&#xff08;如OLED&#xff09;形成…

[測試]并發模擬工具Apache Bench 進行AB壓力測試

下載(windows) https://www.apachelounge.com/download/ 下載后解壓&#xff0c;解壓后進入bin目錄&#xff0c;打開CMD&#xff0c;即可使用 命令 ab.exe -n 請求總數 -c 并發數 http://網站/ 比如ab.exe -n 1000 -c 100 http://127.0.0.1:5555/ 看不懂的話直接把結果讓AI分析…

LeetCode 熱題 100 138. 隨機鏈表的復制

LeetCode 熱題 100 | 138. 隨機鏈表的復制 大家好&#xff0c;今天我們來解決一道經典的鏈表問題——隨機鏈表的復制。這道題在 LeetCode 上被標記為中等難度&#xff0c;要求深拷貝一個帶有隨機指針的鏈表。 問題描述 給你一個長度為 n 的鏈表&#xff0c;每個節點包含一個額…

開源分享:TTS-Web-Vue系列:Vue3實現固定頂部與吸頂模式組件

&#x1f3af; 本文是TTS-Web-Vue系列的第十三篇文章&#xff0c;重點介紹項目中固定頂部導航和內容區域吸頂模式的實現方案。通過這些優化&#xff0c;我們大幅提升了用戶在滾動頁面時的交互體驗&#xff0c;使關鍵操作區域始終可見&#xff0c;同時實現了更現代化的界面視覺效…

Docker、Docker-compose、K8s、Docker swarm之間的區別

1.Docker docker是一個運行于主流linux/windows系統上的應用容器引擎&#xff0c;通過docker中的鏡像(image)可以在docker中構建一個獨立的容器(container)來運行鏡像對應的服務&#xff1b; 例如可以通過mysql鏡像構建一個運行mysql的容器&#xff0c;既可以直接進入該容器命…

用瀏覽器打開pdf,如何使用劃詞翻譯?

1. 瀏覽器 | 擴展 | 獲取 Microsoft Edge 擴展 2. 搜索 “沙拉查詞” 點擊“獲取” 3. 擴展這里選擇 管理擴展 勾選 “允許訪問文件url” 注&#xff1a;這里一定要勾選&#xff0c;否則沙拉查詞無法訪問.pdf 文件&#xff01;&#xff01;&#xff01;會出現下圖錯誤 4. 右擊…

深入解析STM32中斷機制:從原理到外部中斷實戰

知識點1【中斷的介紹】 單片機的中斷——硬件中斷 Linux操作系統的中斷——軟件中斷 中斷是指計算機運行過程中&#xff0c;出現某種意外情況需要主機干預&#xff0c;機器能自動停止正在運行的程序并轉入處理新情況的程序&#xff0c;處理完畢后有返回原本暫停的程序繼續運…

【入門】打印字母塔

描述 輸入行數N,打印圖形. 輸入描述 輸入只有一行&#xff0c;包括1個整數。(N<15) 輸出描述 輸出有N行. #include <bits/stdc.h> using namespace std; int main() { char t;int n,f;cin>>n;for(int i1;i<n;i){tchar(65i);for(int j1;j<n-i;j){cout…

CentOS 7.9 安裝詳解:手動分區完全指南

CentOS 7.9 安裝詳解&#xff1a;手動分區完全指南 為什么需要手動分區&#xff1f;CentOS 7.9 基本分區說明1. /boot/efi 分區2. /boot 分區3. swap 交換分區4. / (根) 分區 可選分區&#xff08;進階設置&#xff09;5. /home 分區6. /var 分區7. /tmp 分區 分區方案建議標準…

油冷式電動滾筒設計:關鍵技術解析與應用前景

引言 電動滾筒作為一種集動力傳輸、減速和驅動功能于一體的機電一體化設備&#xff0c;在輸送機械、礦山設備、食品加工等領域廣泛應用。隨著工業設備向高效化、緊湊化和智能化發展&#xff0c;傳統風冷式電動滾筒的散熱效率與負載能力已逐漸難以滿足需求。油冷式電動滾筒憑借…

Android開發-Activity附加信息

在Android應用開發中&#xff0c;除了基本的界面跳轉和數據傳遞之外&#xff0c;我們還經常需要為Activity添加一些附加信息&#xff08;Metadata&#xff09;&#xff0c;以支持更復雜的配置需求或與系統進行交互。這些附加信息可以通過<meta-data>標簽在AndroidManifes…

2025第九屆御網杯網絡安全大賽線上賽 區域賽WP (MISC和Crypto)(詳解-思路-腳本)

蕪湖~ 御網杯線上分是越來越精細 區域賽都有了 然后不過多評價 整體不算難 以下是我自己的一些思路和解析 有什么問題或者建議隨時都可以聯系我 目錄 蕪湖~ MISC #被折疊的顯影圖紙 #光隙中的寄生密鑰 #ez_xor #套娃 #easy_misc #ez_pictre Crypto #easy簽到題 …

?中繼器:網絡中的“血包”與“加時器”?

在探討網絡技術時&#xff0c;我們往往會遇到各種專業術語和設備&#xff0c;中繼器便是其中之一。然而&#xff0c;對于非技術人員或初學者來說&#xff0c;這些概念可能顯得抽象且難以理解。今天&#xff0c;我將通過一個生動的比喻——將中繼器比作網絡中的“血包”與“加時…

MySQL----高級查詢

目錄標題 ?**多表查詢的格式**?**查詢前說明**一.**使用內連接**inner join**進行多表查詢****1.介紹****2.事例** 二.**使用外連接**outer join**進行多表查詢**1.**介紹** ?多表查詢的格式 其一 select *&#xff5c;字段列表 from 表1[查詢類型] join 表名2 on 連接條件…

SpringBoot主入口類分析

1 &#xff09;SpringBoot主入口類 SpringBoot 主入口類如下所示&#xff0c;這個類的main方法就是整個springboot項目的入口。 package com.example.demo3;import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootA…

【RabbitMQ】 RabbitMQ高級特性(一)

文章目錄 一、消息確認1.1、消息確認機制1.2、手動確認方法1.2.1、AcknowledgeMode.NONE1.2.2、AcknowledgeMode.AUTO1.3.3、AcknowledgeMode.MANUAL 二、持久性2.1、 交換機持久化2.2、隊列持久化2.3、消息持久化 三、發送方確認3.1、confirm確認模式3.2、return退回模式3.3、…

探索Hello Robot開源移動操作機器人Stretch 3的技術亮點與市場定位

Hello Robot 推出的 Stretch 3 機器人憑借其前沿技術和多功能性在眾多產品中占據優勢。Stretch 3 機器人采用開源設計&#xff0c;為開發者提供了靈活的定制空間&#xff0c;能夠滿足各種不同的需求。其配備的靈活手腕組件和 Intel Realsense D405 攝像頭&#xff0c;顯著增強了…

expo多網絡請求設定。

在使用 npx expo start 啟動 Expo 開發服務器時&#xff0c;你可以通過設置網絡模式來控制你的應用如何連接到開發服務器。Expo 提供了幾種網絡模式供你選擇&#xff1a; LAN (Default): 這是默認模式。在這種模式下&#xff0c;你的應用會通過本地局域網 (LAN) 連接到你的開發…

Nginx 安全防護與HTTPS部署

目錄 一、核心安全配置 1、隱藏版本號 2、限制危險請求方法 3、請求限制&#xff08;CC攻擊防御&#xff09; &#xff08;1&#xff09;使用Nginx的limit_req模塊限制請求速率 &#xff08;2&#xff09;壓力測試驗證 4、防盜鏈 &#xff08;1&#xff09;修改 Window…

windows 環境下 python環境安裝與配置

運行環境安裝 第一步安裝包下載 python開發工具安裝包下載官網&#xff1a; https://www.python.org/ 根據自己的實際需求選擇。 這里記錄了各個版本的區別和差異。根據區別和差異選擇適合自己的版本。 Windows Installer和Windows embeddable package是兩種不同的軟件包類…