2017-2018-1 20155208 《信息安全系統設計基礎》第八週學習總結

2017-2018-1 20155208 《信息安全系統設計基礎》第八週學習總結

第八週課下測試2(課上沒完成的同窗必作)

服務器源代碼:git

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/signal.h> #include <sys/wait.h> #include <errno.h> #include <time.h> #define PORT 1234 #define BACKLOG 2 #define MAXDATASIZE 1000 void process_cli(int connectfd,struct sockaddr_in client); void sig_handler(int s); int main() { int opt,listenfd,connectfd; pid_t pid; struct sockaddr_in server; struct sockaddr_in client; int sin_size; struct sigaction act; struct sigaction oact; act.sa_handler=sig_handler; sigemptyset(&act.sa_mask); act.sa_flags=0; printf("服務器實現者學號:20155208\n"); if(sigaction(SIGCHLD,&act,&oact)<0) { perror("Sigaction failed!\n"); exit(1); } if((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1) { perror("Creating socket failed.\n"); exit(1); } opt=SO_REUSEADDR; setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); bzero(&server,sizeof(server)); server.sin_family=AF_INET; server.sin_port=htons(PORT); server.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(listenfd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1) { perror("Bind error.\n"); exit(1); } if(listen(listenfd,BACKLOG)==-1) { perror("listen() error.\n"); exit(1); } sin_size=sizeof(struct sockaddr_in); while(1) { if((connectfd=accept(listenfd,(struct sockaddr *)&client,&sin_size))==-1) { if(errno==EINTR) continue; perror("accept() error.\n"); exit(1); } if((pid=fork())>0) { close(connectfd); continue; } else if(pid==0) { close(listenfd); process_cli(connectfd,client); exit(0); } else { printf("fork error.\n"); exit(1); } } close(listenfd); return 0; } void process_cli(int connectfd,struct sockaddr_in client) { int i,num; char recvbuf[MAXDATASIZE]; char sendbuf[MAXDATASIZE]; char cli_name[MAXDATASIZE]; time_t t; t=time(NULL); printf("客戶端IP:%s \n",inet_ntoa(client.sin_addr)); num=recv(connectfd,cli_name,MAXDATASIZE,0); if(num==0) { close(connectfd); printf("Client disconnected.\n"); return; } send(connectfd,(void *)&t,sizeof(time_t),0); while(num=recv(connectfd,recvbuf,MAXDATASIZE,0)) { recvbuf[num]='\0'; printf("當前時間:%s\n",ctime(&t)); //send(connectfd,(void *)&t,sizeof(time_t),0); } close(connectfd); } void sig_handler(int s) { pid_t pid; int stat; while((pid=waitpid(-1,&stat,WNOHANG))>0) printf("子進程 %d 關閉。\n",pid); return; }

客戶端源代碼:程序員

#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdlib.h> #include <time.h> #define PORT 1234 #define MAXDATASIZE 1000 void process(FILE *fp,int sockfd); char *getMessage(char *sendline,int len,FILE *fp); int main(int argc,char *argv[]) { int fd; struct hostent *he; struct sockaddr_in server; if(argc!=2) { printf("Usage: %s <IP Address>\n",argv[0]); exit(1); } if((he=gethostbyname(argv[1]))==NULL) { printf("gethostbyname error.\n"); exit(1); } if((fd=socket(AF_INET,SOCK_STREAM,0))==-1) { perror("socket() error.\n"); exit(1); } bzero(&server,sizeof(server)); server.sin_family=AF_INET; server.sin_port=htons(PORT); server.sin_addr=*((struct in_addr *)he->h_addr); if(connect(fd,(struct sockaddr *)&server,sizeof(struct sockaddr))==-1) { perror("connect() error.\n"); exit(1); } process(stdin,fd); close(fd); return 0; } void process(FILE *fp,int sockfd) { char sendbuf[MAXDATASIZE]; char recvbuf[MAXDATASIZE]; int num; time_t t; t=time(NULL); printf("用戶名:\n"); if(fgets(sendbuf,MAXDATASIZE,fp)==NULL) { printf("lease enter your name,now you have exit.\n"); return; } send(sockfd,sendbuf,strlen(sendbuf),0); while(getMessage(sendbuf,MAXDATASIZE,fp)!=NULL) { send(sockfd,sendbuf,strlen(sendbuf),0); if((num=recv(sockfd,recvbuf,MAXDATASIZE,0))==0) { printf("Server no send you any data.\n"); return; } recvbuf[num]='\0'; printf("服務器消息:%s\n",ctime(&t)); } printf("Exit.\n"); } char *getMessage(char *sendline,int len,FILE *fp) { printf("請輸入請求:\n"); return(fgets(sendline,len,fp)); }
  • 運行成功截圖:

第八週課下測試1

教材學習及總結

第十一章 網絡編程

第一節 客戶端-服務端編程模型

  • 一個應用是由一個服務器進程和一個或者多個客戶端進程組成的web

  • 客戶端和服務端是進程算法

第二節 網絡

  • 對於一個主機而言,網絡只是又一種I/O設備數據庫

  • 物理上而言,網絡是一個按照地理遠近組成的層次系統。編程

  • 最底層是LAN(局域網) 適配器提供到網絡的物理接口 以太網段(電纜+集線器)瀏覽器

  • 一臺主機能夠發送一段位,稱爲幀 每一個主機適配器都能看到這個幀,可是隻有目的主機實際讀取它 多個以太網段能夠鏈接成較大的局域網,稱爲橋接以太網 網橋比集線器更充分的利用了電纜帶寬 互聯網絡安全

  • 在層次較高的級別中,多個不兼容的局域網能夠經過路由器鏈接起來,組成互聯網絡 每臺路由器對於它所鏈接到的每一個網絡都有一個適配器(端口) WAN(廣域網) 路由器能夠用來由各類局域網和廣域網構建互聯網絡服務器

  • 網絡協議提供兩種基本能力:網絡

    命名機制

    傳送機制

    封裝是關鍵

第三節 全球IP因特網

  • 因特網的客戶端和服務端混合使用套接字接口函數和Unix I/O函數來進行通訊

  • 因特網上的主機經過IP地址和域名來標識

  • TCP/IP其實是一個協議族

  • IP機制從某種意義上而言是不可靠的 TCP是一個構建在IP之上的複雜協議,提供了進程間可靠地全雙工鏈接

1、IP地址

一個IP地址就是一個32位無符號整數

IP/TCP爲任意整數數據項定義了統一的網絡字節順序(大端字節順序)

對inet-aton的調用傳遞的是指向結構的指針,而對inet_ntoa的調用傳遞的是結構自己

2、因特網域名

域名集合造成一個層次結構,子樹稱爲子域

因特網應用程序經過調用gethostbyname函數和gethostbyaddr函數,從DNS數據庫中檢索任意的主機條目

gethostbyname函數:返回和域名name相關的主機條目 gethostbyaddr函數:返回和IP地址相關聯的主機條目 一個IP對多個域名,可供多個域名解析,但域名解析到的地址是一個對一個

3、因特網鏈接

點對點、全雙工、可靠

客戶端套接字地址中的端口是由內核自動分配的,稱爲臨時端口

服務端套接字地址中的端口一般是某個知名端口(HTTP:80)

套接字地址:(地址:端口)

第四節 套接字接口

  • 套接字接口是一組函數,用以建立網絡應用

  • 套接字地址結構

sinfamily成員是AFINET

sin_port成員是一個16位端口

sin_addr成員是32位的IP地址

IP地址和端口號老是以網絡字節順序(大端法)存放的

socket函數

客戶端和服務端使用socket函數來建立一個套接字描述符

connect函數

創建和服務器的鏈接

open_clientfd函數

將socket和connect函數包裝而成。客戶端能夠用它來和服務器創建鏈接 bind函數、listen函數、accept函數均被服務器用於和客戶端創建鏈接

open_listenfd函數

socket、bind和listen函數結合。用於服務器建立一個監聽描述符

第五節 Web服務器

1.協議

Web 客戶端和服務器之間的交互用的是一個基於文本的應用級協議,叫作 HTTP (超文本傳輸協議)

HTTP 是一個簡單的協議

一個 Web 客戶端(即瀏覽器) 打開一個到服務器的因特網鏈接,而且請求某些內容。服務器響應所請求的內容,而後關閉鏈接。瀏覽器讀取這些內容,並把它顯示在屏幕上

Web內容能夠用一種叫作 HTML(Hypertext Markup Language,超文本標記語言)的語言來編寫。一個 HTML 程序(頁)包含指令(標記),它們告訴瀏覽器如何顯示這頁中的各類文本和圖形對象

2.內容

對於Web客戶端和服務端而言,內容是與一個MIME類型相關的字節序列

Web服務器以兩種不一樣的方式向客戶端提供內容:

取一個磁盤文件,並將它的內容返回給客戶端。磁盤文件稱爲靜態內容 , 而返回文件給客戶端的過程稱爲服務靜態內容 運行一個可執行文件,並將它的輸出返回給客戶端。運行時可執行文件產生的輸出稱爲態內容 ,而運行程序並返回它的輸出到客戶端的過程稱爲服務動態內容 每條由Web服務器返回的內容都是和他管理的某個文件相關聯的。這些文件每個都有一個惟一的名字,叫作:URL

第十二章 併發編程

程序級併發——進程

函數級併發——線程 三種基本的構造併發程序的方法:

進程:每一個邏輯控制流是一個進程,由內核進行調度,進程有獨立的虛擬地址空間

I/O多路複用:邏輯流被模型化爲狀態機,全部流共享同一個地址空間

線程:運行在單一進程上下文中的邏輯流,由內核進行調度,共享同一個虛擬地址空間

第一節 基於進程的併發編程

構造併發程序最簡單的方法——用進程 經常使用函數以下:

fork exec waitpid 構造併發服務器 在父進程中接受客戶端鏈接請求,而後建立一個新的子進程來爲每一個新客戶端提供服務。

須要注意的事情: 1.父進程須要關閉它的已鏈接描述符的拷貝(子進程也須要關閉)

2.必需要包括一個SIGCHLD處理程序來回收僵死子進程的資源

3.父子進程之間共享文件表,可是不共享用戶地址空間,這個在之前的學習過程當中提到過

關於獨立地址空間

1.優勢:防止虛擬存儲器被錯誤覆蓋

2.缺點:開銷高,共享狀態信息才須要IPC機制

第二節 基於I/O多路複用的併發編程

I/O多路複用技術基本思想:使用select函數要求內核掛起進程,只有在一個或多個I/O事件發生後,纔將控制返回給應用程序。

select函數處理類型爲fd_set的集合,即描述符集合,並在邏輯上描述爲一個大小爲n的位向量,每一位b[k]對應描述符k,但當且僅當b[k]=1,描述符k才代表是描述符集合的一個元素。

描述符能作的三件事:

1.分配他們

2.將一個此種類型的變量賦值給另外一個變量

3.用FDZERO、FDSET、FDCLR和FDISSET宏指令來修改和檢查它們

何時能夠讀? 當且僅當一個從該描述符讀取一個字節的請求不會阻塞時

注意: 每次調用select函數時都須要更新讀集合

1、基於I/O多路複用的併發事件驅動服務器

事件驅動程序:將邏輯流模型化爲狀態機。

狀態機:

狀態
輸入事件
轉移 對於狀態機的理解,參考EDA課程中學習的狀態轉換圖的畫法和狀態機。

總體的流程是:

select函數檢測到輸入事件 add_client函數建立新狀態機 check_clients函數執行狀態轉移(在課本的例題中是回送輸入行),而且完成時刪除該狀態機。 幾個須要注意的函數:

init_pool:初始化客戶端池 add_client:添加一個新的客戶端到活動客戶端池中 check_clients:回送來自每一個準備好的已鏈接描述符的一個文本行 2、I/O多路複用技術的優劣

1.優勢

相較基於進程的設計,給了程序員更多的對程序程序的控制 運行在單一進程上下文中,因此每一個邏輯流均可以訪問該進程的所有地址空間,共享數據容易實現 可使用GDB調試 高效 2.缺點

編碼複雜 不能充分利用多核處理器 第三節 基於線程的併發編程

這種模式混合了以上兩種方法

線程:就是運行在進程上下文中的邏輯流。

每一個線程都有它本身的線程上下文:

一個惟一的整數線程ID——TID 棧 棧指針 程序計數器 通用目的寄存器 條件碼 1、線程執行模型

1.主線程

在每一個進程開始生命週期時都是單一線程——主線程,與其餘進程的區別僅有:它老是進程中第一個運行的線程。

2.對等線程

某時刻主線程建立,以後兩個線程併發運行。

每一個對等線程都能讀寫相同的共享數據。

3.主線程切換到對等線程的緣由:

主線程執行一個慢速系統調用,如read或sleep 被系統的間隔計時器中斷 切換方式是上下文切換

對等線程執行一段時間後會控制傳遞迴主線程,以此類推

4.線程和進程的區別

線程的上下文切換比進程快得多 組織形式: 進程:嚴格的父子層次 線程:一個進程相關線程組成對等(線程)池,和其餘進程的線程獨立開來。一個線程能夠殺死它的任意對等線程,或者等待他的任意對等線程終止。 2、Posix線程

Posix線程是C程序中處理線程的一個標準接口。基本用法是:

線程的代碼和本地數據被封裝在一個線程例程中 每一個線程例程都以一個通用指針爲輸入,並返回一個通用指針。 這裏須要提到一個萬能函數的概念。

萬能函數:

void func(void parameter) typedef void (uf)(void para)

即,輸入的是指針,指向真正想要傳到函數裏的數據,若是隻有一個就直接讓指針指向這個數據,若是是不少就將它們 放到一個結構體中,讓指針指向這個結構體。後面這個方法就是萬能函數的使用思想。 線程例程也是這樣的。

3、建立線程

1.建立線程:pthread_create函數

#include <pthread.h> typedef void *(func)(void *);

int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);

成功返回0,出錯返回非0 建立一個新的線程,帶着一個輸入變量arg,在新線程的上下文運行線程例程f。

attr默認爲NULL

參數tid中包含新建立線程的ID

2.查看線程ID——pthread_self函數

#include <pthread.h>

pthread_t pthread_self(void);

返回調用者的線程ID(TID)

4、終止線程

1.終止線程的幾個方式:

隱式終止:頂層的線程例程返回 顯示終止:調用pthread_exit函數 *若是主線程調用,會先等待全部其餘對等線程終止,再終止主線程和整個進程,返回值爲pthread_return

某個對等線程調用Unix的exit函數,會終止進程與其相關線程 另外一個對等線程經過以當前線程ID做爲參數調用pthread_cancle來終止當前線程 2.pthread_exit函數

#include <pthread.h> void pthread_exit(void *thread_return);

若成功返回0,出錯爲非0 3.pthread_cancle函數

#include <pthread.h> void pthread_cancle(pthread_t tid);

若成功返回0,出錯爲非0

5、回收已終止線程的資源

用pthread_join函數:

#include <pthread.h> int pthread_join(pthread_t tid,void **thrad_return);

這個函數會阻塞,知道線程tid終止,將線程例程返回的(void*)指針賦值爲thread_return指向的位置,而後回收已終止線程佔用的全部存儲器資源

6、分離線程

在任何一個時間點上,線程是可結合的,或是分離的。

1.可結合的線程

可以被其餘線程收回其資源和殺死 被收回錢,它的存儲器資源沒有被釋放 每一個可結合線程要麼被其餘線程顯式的收回,要麼經過調用pthread_detach函數被分離 2.分離的線程

不能被其餘線程回收或殺死 存儲器資源在它終止時由系統自動釋放 3.pthread_detach函數

#include <pthread.h>

void pthread_detach(pthread_t tid);

若成功返回0,出錯爲非0 這個函數能夠分離可結合線程tid。

線程可以經過以pthreadself()爲參數的pthreaddetach調用來分離他們本身。

每一個對等線程都應該在他開始處理請求以前分離他自身,以使得系統能在它終止後回收它的存儲器資源。

7、初始化線程:pthread_once函數

#include <pthread.h> pthread_once_t once_control = PTHREAD_ONCE_INIT;

int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));

老是返回0

8、基於線程的併發服務器中的注意事項

1.調用pthread_create時,如何將已鏈接描述符傳遞給對等進程?

傳遞指針。

2.競爭問題

見第七節。

3.避免存儲器泄露

必須分離每一個線程,使它終止時它的存儲器資源能被收回。

第四節 多線程程序中的共享變量

一個變量是共享的,當且僅當多個線程引用這個變量的某個實例。

1、線程存儲器模型

須要注意的有:

寄存器從不共享,虛擬存儲器老是共享的。

2、將變量映射到存儲器

3、共享變量

變量v是共享的——當且僅當它的一個實例被一個以上的線程引用。

第五節 用信號量同步線程

通常而言,沒有辦法預測操做系統是否將爲你的線程選擇一個正確的順序。

1、進度圖

進度圖是將n個併發線程的執行模型化爲一條n維笛卡爾空間中的軌跡線,原點對應於沒有任何線程完成一條指令的初始狀態。

當n=2時,狀態比較簡單,是比較熟悉的二維座標圖,橫縱座標各表明一個線程,而轉換被表示爲有向邊

轉換規則:

合法的轉換是向右或者向上,即某一個線程中的一條指令完成 兩條指令不能在同一時刻完成,即不容許出現對角線 程序不能反向運行,即不能出現向下或向左 而一個程序的執行歷史被模型化爲狀態空間中的一條軌跡線。

線程循環代碼的分解:

H:在循環頭部的指令塊 L:加載共享變量cnt到線程i中寄存器%eax的指令。 U:更新(增長)%eax的指令 S:將%eax的更新值存回到共享變量cnt的指令 T:循環尾部的指令塊 幾個概念

臨界區:對於線程i,操做共享變量cnt內容的指令L,U,S構成了一個關於共享變量cnt的臨界區。 不安全區:兩個臨界區的交集造成的狀態 安全軌跡線:繞開不安全區的軌跡線 具體相關在操做系統課程中講的更爲詳細,好比:

臨界區使用原則(互斥條件)

有空讓進:若是臨界區空閒,則只要有進程申請就當即讓其進入; 無空等待:每次只容許一個進程處於臨界區; 多中擇一:當沒有進程在臨界區,而同時有多個進程要求進入臨界區,只能讓其中之一進入臨界區,其餘進程必須等待; 讓權等待:進入臨界區的進程,不能在臨界區內長時間阻塞等待某事件,使其它進程在臨界區外無限期等待; 不能限制進程的併發數量和執行進度。

2、信號量

信號量實現互斥的基本原理

兩個或多個進程經過傳遞信號進行合做,能夠迫使進程在某個位置暫時中止執行(阻塞等待),直到它收到一個能夠「向前推動」的信號(被喚醒); 將實現信號燈做用的變量稱爲信號量,常定義爲記錄型變量s,其一個域爲整型,另外一個域爲隊列,其元素爲等待該信號量的阻塞進程(FIFO)。 信號量定義:

type semaphore=record count: integer; queue: list of process end; var s:semaphore; 定義對信號量的兩個原子操做——P和V

P(wait)

wait(s) s.count :=s.count-1; if s.count<0 then begin 進程阻塞; 進程進入s.queue隊列; end;

V(signal)

signal(s) s.count :=s.count+1; if s.count ≤0 then begin 喚醒隊首進程; 將進程從s.queue阻塞隊列中移出; end; 須要注意的是,每一個信號量在使用前必須初始化。

3、使用信號量來實現互斥

1.基本思想

將每一個共享變量(或者一組相關的共享變量)與一個信號量s(初始爲1)聯繫起來,而後用P和V操做將相應的臨界區包圍起來。

2.幾個概念

二元信號量:用這種方式來保護共享變量的信號量叫作二元信號量,取值老是0或者1 互斥鎖:以提供互斥爲目的的二元信號量 加鎖:對一個互斥鎖執行P操做 解鎖;對一個互斥鎖執行V操做 計數信號量:被用做一組可用資源的計數器的信號量 禁止區:因爲信號量的不變性,沒有實際可能的軌跡可以包含禁止區中的狀態。

3.wait(s)/signal(s)的應用

進程進入臨界區以前,首先執行wait(s)原語,若s.count<0,則進程調用阻塞原語,將本身阻塞,並插入到s.queue隊列排隊; 注意,阻塞進程不會佔用處理機時間,不是「忙等」。直到某個從臨界區退出的進程執行signal(s)原語,喚醒它; 一旦其它某個進程執行了signal(s)原語中的s.count+1操做後,發現s.count ≤0,即阻塞隊列中還有被阻塞進程,則調用喚醒原語,把s.queue中第一個進程修改成就緒狀態,送就緒隊列,準備執行臨界區代碼。 以及

wait操做用於申請資源(或使用權),進程執行wait原語時,可能會阻塞本身; signal操做用於釋放資源(或歸還資源使用權),進程執行signal原語時,有責任喚醒一個阻塞進程。

3、利用信號量來調度共享資源

信號量有兩個做用:

實現互斥

調度共享資源

信號量分爲:互斥信號量和資源信號量。

互斥信號量用於申請或釋放資源的使用權,常初始化爲1;

資源信號量用於申請或歸還資源,能夠初始化爲大於1的正整數,表示系統中某類資源的可用個數。

1.信號量的物理意義

s.count >0表示還可執行wait(s)而不會阻塞的進程數(可用資源數)。每執行一次wait(s)操做,就意味着請求分配一個單位的資源。

當s.count ≤0時,表示已無資源可用,所以請求該資源的進程被阻塞。此時,s.count的絕對值等於該信號量阻塞隊列中的等待進程數。執行一次signal操做,就意味着釋放一個單位的資源。若s.count<0,表示s.queue隊列中還有被阻塞的進程,須要喚醒該隊列中的第一個進程,將它轉移到就緒隊列中。

2.常見問題

這裏的常見問題有生產者-消費者問題,和讀者-寫者問題,都是操做系統課程中詳細講述過的,再也不贅述。

第七節 其餘併發問題

1、線程安全性

一個線程是安全的,當且僅當被多個併發線程反覆的調用時,它會一直產生正確的結果。

四個不相交的線程不安全函數類以及應對措施:

不保護共享變量的函數——用P和V這樣的同步操做保護共享變量 保持跨越多個調用的狀態的函數——重寫,不用任何static數據。 返回指向靜態變量的指針的函數——①重寫;②使用加鎖-拷貝技術。 調用線程不安全函數的函數——參考以前三種

2、可重入性

當它們被多個線程調用時,不會引用任何共享數據。

1.顯式可重入的:

全部函數參數都是傳值傳遞,沒有指針,而且全部的數據引用都是本地的自動棧變量,沒有引用靜態或全劇變量。

2.隱式可重入的:

調用線程當心的傳遞指向非共享數據的指針。

3、在線程化的程序中使用已存在的庫函數

一句話,就是使用線程不安全函數的可重入版本,名字以_r爲後綴結尾。

4、競爭

1.競爭發生的緣由:

一個程序的正確性依賴於一個線程要在另外一個線程到達y點以前到達它的控制流中的x點。也就是說,程序員假定線程會按照某種特殊的軌跡穿過執行狀態空間,忘了一條準則規定:線程化的程序必須對任何可行的軌跡線都正確工做。

2.消除方法:

動態的爲每一個整數ID分配一個獨立的塊,而且傳遞給線程例程一個指向這個塊的指針

5、死鎖:

一組線程被阻塞了,等待一個永遠也不會爲真的條件。

解決死鎖的方法

1.不讓死鎖發生:

靜態策略:設計合適的資源分配算法,不讓死鎖發生---死鎖預防; 動態策略:進程在申請資源時,系統審查是否會產生死鎖,若會產生死鎖則不分配---死鎖避免。 2.讓死鎖發生:

進程申請資源時不進行限制,系統按期或者不按期檢測是否有死鎖發生,當檢測到時解決死鎖----死鎖檢測與解除。

代碼託管

代碼上傳截圖:

碼雲連接

學習進度條

  代碼行數(新增/積累) 博客量(新增/積累 學習時間(新增/累積)
目標 5000行 30篇 400小時
第一週 5/5 1/1 8/8
第二週 120/120 1/1 12/12
第三週 100/100 1/1 15/15
第四周 80/80 1/1 9/9
第五週 50/50 1/1 6/6
第六週 350/350 1/1 12/12
第七週 120/120 2/2 15/15
第八週 380/380 1/1 16/16

參考資料

《深刻理解計算機系統V3》學習指導

相關文章
相關標籤/搜索