1.操作系統與Linux io框架
1.1 io與操作系統
1.1.1 io概念
- io 描述的是硬件設備之間的數據交互,分為輸? (input) 與輸出 (output)。
- 輸?:應?程序從其他設備獲取數據 (read) 暫存到內存設備中;
- 輸出:應?程序將內存暫存的數據寫?到其他設備 (write)。
1.1.2 操作系統概念
-
操作系統通常包含兩種不同的含義
-
第?種含義: 指完整的軟件包 : 包括核?軟件與應?軟件。
- 應?軟件: 命令解釋器, 圖形?戶界?, ?件操作?具與?件編輯器;
- 核?軟件: 管理和分配計算機資源 (這些計算機資源即 cpu,RAM, 其他設備),即操作系統核?軟件 (內核)。
-
第?種含義: 專指操作系統核?軟件 (內核)。我們以后就按第二種含義來理解。
-
內核的職責如下:
- 進程管理 : 分配 cpu 資源,?于執?程序指令;
- 內存管理 : 如今計算機內存容量可謂相當可觀, 但軟件的規模也保持了相應的增?, 內存資源仍然屬于有限資源, 內核必須公平與?效的管理內存資源, 其中虛擬內存管理技術;
- ?件管理 : 允許對?件執?創建,獲取,更新以及刪除;
- 設備管理 : 計算機外界設備可實現計算機與外部世界的通訊;
- 聯?管理 : 使計算機可以進??絡通訊;
- 提供應?編程接? (API): 進程可利?內核??點請求內核區執?各種任務。
-
1.1.3 Linux操作系統結構
-
Linux操作系統結構
-
?般分為?戶層與內核層
- ?戶層 : 表示在內核層之上的庫 (如 glibc) 與 應?程序 (app);
- 內核層 : 操作系統內核;
-
?戶層與內核層是相輔相成,?戶層的應?程序依賴于庫或者內核, 庫與內核給應?層提供服務;
-
內核通過系統調?來給應?層提供接?。
-
-
系統調用與庫函數
- 系統調?是 Linux 內核提供給應?程序的訪問接?, 當需要 Linux 內核提供服務時, 則需要訪問系統調?。
- 庫函數是為了實現某個功能?封裝起來的 API 集合, 能夠提供統?的編程接?,更加便于應?程序的移植。
- glibc 是屬于 GNU(GNU’s Not unix) ?程的?部分, 這個?程當初的?標是為了開發?款完整的操作系統, 但在開發過程中將除了 Linux 內核以外的組件都開發完成, 由于難度很?, 開發周期?, 在 1992 年 由 Linus Torvalds 開發出來了 Linux 內核, 填補了 GNU 系統的?個重要空?, 所有后?將 GNU 組件與 Linux 合并組成現在的 GNU/Linux。
- glibc 包含 標準 c 庫函數集合 和 系統調?
- 標準的 c 庫函數是跨平臺的,既可以在 Linux 系統下調?, 也可以在 windows 系統下調?;
- 系統調?是 Linux 內核給?戶提供的訪問接?, 但在 glibc 中封裝了系統調?接??形成了 glibc 的庫函數;
- glibc 庫函數主要是封裝了系統調?的過程, 相應的系統調??般實現在 Linux 內核中;
- ?般的 glibc 中的庫函數都會與系統調?關聯,但也有庫函數不需要使?系統調?,?如字符串操作函數。
1.2 Linux io框架
1.2.1 Linux io框架介紹
-
Linux io 框架也是分層設計, 這?以將內存中的數據存儲到硬盤中為例
-
應?程序通過調?操作系統提供的 io 接? (函數) 向內核進? io 請求 , 由內核最終完整相應的io操作;
-
Linux io 框架基于?切皆?件的思想來設計;
- ?的 : 屏蔽底層不同設備之間的 io 差異, 給應?層提供統?的操作接?;
- 思想 : 即將底層的 io 操作統?抽象成?件操作,操作提供系統只需要提供?組?件 io操作接?就可以為應?程序提供 io 服務。
-
?件 io 操作主要包含:
- open:打開
- close:關閉
- read:讀取
- write:寫?
- lseek:定位
-
?件 io 接?的設計本身來沿?了?的 操作習慣
- ?腦相當于 內存設備
- 書籍或者其他筆記本相當于另?個設備
- 讀書 : 相當于?腦獲取數據 (read)
- 寫字 : 相當于將?腦數據寫?到其他存儲介質中
-
下?以 printf io 過程為例來說明
-
printf io 的過程本質上是將暫存在內存中的數據寫?到顯示器中;
-
printf 函數?先會調? glibc 中 write 函數來發出 io 請求;
-
write 函數在通過調?由操作系統內核提供的系統調? sys_write 函數最終完成 io 操作.
-
下?是 sys_write 系統調?在內核中的實現
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, size_t, count) {struct fd f = fdget_pos(fd);ssize_t ret = -EBADF;if (f.file) {loff_t pos = file_pos_read(f.file);ret = vfs_write(f.file, buf, count, &pos);if (ret >= 0)file_pos_write(f.file, pos);fdput_pos(f);} return ret; }
-
2.Linux文件io接口
2.1 Linux文件io簡介
2.1.1 關于Linux文件io
- 在 Linux 系統下, ?于對?件操作的庫函數叫做?件 I/O;
- 主要包括 open()/close()/read()/write() /lseek() 相應的系統調?(準確說法是對系統調?的封裝的庫函數)。
2.1.2 文件描述符
-
?件描述符是?個?負整數 , 當打開?個已存在?件或者創建?個新?件時, 內核向進程返回?個?件描述符;
-
每個程序運?后, 操作系統會默認打開三個?件(標準輸?、標準輸出、標準錯誤輸出) , ?件描述符分別為 0 , 1 , 2;
- 標準輸?對應的設備?般為鍵盤;
- 標準輸出與標準錯誤輸出設備?般為顯示器;
-
示例:通過 write 函數 (后?會詳細講解) 使?標準輸出來打印 Hello world。
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> int main(int argc,char *argv[]) {write(1,"helloworld",10); return 0; }
2.2 Linux文件io操作
2.2.1 open函數
-
open函數說明
-
函數功能
- 打開文件,并得到文件描述符。
-
函數原型
int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode);
-
頭文件說明
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h>
-
參數說明
- pathname : ?件路徑名
- flags : 打開標志
- O_RDONLY: 只讀?式打開?件(read only)
- O_WRONLY: 可寫?式打開?件(write only)
- O_RDWR: 讀寫?式打開?件(read write)
- O_CREAT: 如果該?件不存在就創建?個新的?件,并?第三的參數為其設置權限
- O_EXCL: 如果使? O_CRATE 時?件存在, open() 報錯(exclusive,排外的)
- O_TRUNC: 如果?件已經存在,并且以讀 / 寫或只寫成功打開, 并清零,即清空文件內容;
- O_APPEND: 以添加的?式打開?件,在打開?件的同時,?件指針指向?件末尾
- mode : 指定創建新的?件的默認權限
-
返回值
成功:返回?件描述符 失敗:返回-1, 并將錯誤編碼保存到 errno
-
-
示例1:通過只讀的?式打開?個?件
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <string.h> int main(int argc,char *argv[]) {int fd; if (argc != 2){ /*int main(int argc, const char *argv[])argc:命令行傳遞參數的個數argv[0]:命令行傳遞的第一個參數argv[1]:命令行傳遞的第二個參數argv[2]:命令行傳遞的第三個參數*/fprintf(stderr,"Usage : < %s > < pathname >\n", argv[0]);return -1; } fd = open(argv[1], O_RDONLY);if (fd == -1){perror("Open(): ");return -1; } close(fd);return 0; }
-
練習:以只寫的?式打開?件, 如果不存在則創建, 如果?件存在則截短(即清空文件內容)
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> int main(int argc,char *argv[]) {int fd; if(argc != 2){printf("Usage : %s <pathname> .\n",argv[0]);}fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0644);if (fd == -1) { perror("open()");exit(-1);} printf("fd = %d\n",fd);close(fd);return 0; }
-
函數錯誤處理與errno
-
errno 是 Linux 操作系統中?于存儲錯誤編碼的全局變量, 錯誤編碼在 Linux 系統中的定義如下:
#define EPERM 1 #define ENOENT 2 #define ESRCH 3 #define EINTR 4 #define EIO 5 #define ENXIO 6 #define E2BIG 7 #define ENOEXEC 8 #define EBADF 9 #define ECHILD 10 #define EAGAIN 11 #define ENOMEM 12 #define EACCES 13 #define EFAULT 14 #define ENOTBLK 15 #define EBUSY 16
-
-
錯誤信息打印
-
錯誤信息打印主要使用perror() 函數。
-
函數頭文件
#include <stdio.h>
-
函數原型
void perror(const char *s)
-
函數參數
s : ?定義字符串參數
-
-
錯誤信息轉換主要使? strerror() 函數, 具體說明如下:
-
函數頭文件
#include <string.h>
-
函數原型
char *strerror(int errnum)
-
函數功能
將錯誤編碼轉換成字符串信息,并返回該字符串的地址。
-
函數參數
errnum : 錯誤編碼
-
函數返回值
返回錯誤碼轉換之后的字符串 or “Unknown error nnn”。
-
示例 : 使? perror 函數打印 出錯信息
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int main(int argc,char *argv[]) {int fd; fd = open(argv[1],O_RDONLY,0644);if (fd == -1) { perror("open(): ");//perror(strerror(EIO));//將錯誤編碼EIO轉換成出錯信息字符串,通過perror函數將出錯信息字符串打印出來return -1; } return 0; }
-
-
2.2.2 close函數
-
close函數說明
-
函數頭文件
#include <unistd.h>
-
函數原型
int close(int fd);
-
函數功能
close 函數?于關閉?件,在 io 操作結束后需要關閉?件,釋放相關資源。
-
函數參數
fd : ?件描述符
-
函數返回值
成功:返回0 失敗:返回-1
-
-
示例:將前?已經打開的?件使? close 函數關閉。
2.2.3 read函數
-
函數頭文件
#include <unistd.h>
-
函數原型
ssize_t read(int fd, void *buf, size_t count)
-
函數功能
從?件中讀取數據保存緩沖區中。
-
函數參數
fd : ?件描述符 buf : 數據緩沖區 count : 能夠讀取的最?字節數
-
函數返回值
成功 : 返回實際讀取的字節數 失敗 : -1, 并將錯誤編碼設置到 errno 中
-
示例:從指定?件中讀取 10 個字節數據,并進?打印
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> int main(int argc,char *argv[]) {int fd;char buffer[64] = {0};ssize_t rbytes;if (argc != 2){fprintf(stderr,"Usage : < %s > < pathname >\n",argv[0]);return -1; }fd = open(argv[1],O_RDONLY); if (fd == -1){ perror("Open(): ");return -1;}rbytes = read(fd,buffer,10);if (rbytes == -1){perror("Read(): ");return -1;}printf("Buffer : %s\n",buffer);close(fd);return 0; }
2.2.4 write函數
-
函數頭文件
#include <unistd.h>
-
函數原型
ssize_t write(int fd, const void *buf, size_t count);
-
函數功能
把緩沖區中的數據寫入到指定文件中。
-
函數參數
fd : ?件描述符 buf : 緩沖區地址 count : 需要寫?的字節數
-
函數返回值
成功: 返回實際成功寫?的字節數 失敗: 返回 -1, 并設置 errno
-
示例:將 ABCDE12345 字符串寫?到指定?件中, 并驗證是否寫?正確
#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> int main(int argc,char *argv[]) {int fd;char buffer[64] = "ABCED12345";ssize_t wbytes;if (argc != 2){fprintf(stderr,"Usage : < %s > < pathname >\n",argv[0]);return -1;}fd = open(argv[1],O_RDWR|O_CREAT);if (fd == -1){perror("Open(): ");return -1;}wbytes = write(fd,buffer,10);if (wbytes == -1){perror("Write(): ");return -1;}close(fd);return 0; }
2.2.5 lseek函數
-
函數原型
off_t lseek(int fd, off_t offset, int whence);
-
函數參數
- fd : ?件描述符
- offset : 偏移量, 可以為正數或者負數
- whence : 偏移相對位置
- SEEK_CUR : 相對于?件當前偏移
- SEEK_SET : 相對于?件開始位置
- SEEK_END : 相對于?件尾偏移
-
函數返回值
- 成功: 返回 0
- 失敗 : 返回 -1, 并設置 errno
- 當前?件的偏移量決定下次 io 操作時的起始位置
- 對于同?個?件描述符,共享同?個偏移量
-
示例:將?個字符串 “hello,linux io” 寫?到?件中,在讀取出來
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <string.h> int main(int argc,char *argv[]) {int fd; char buffer[64] = "hello,Linux io";char rbuffer[64] = {0};ssize_t wbytes = 0,rbytes = 0;if (argc != 2){ fprintf(stderr,"Usage : < %s > < pathname >\n",argv[0]);return -1; } fd = open(argv[1],O_RDWR|O_CREAT);if (fd == -1){perror("Open(): ");return -1; } wbytes = write(fd,buffer,strlen(buffer));if (wbytes == -1){perror("Write(): ");return -1;} lseek(fd,0,SEEK_SET);rbytes = read(fd,rbuffer,wbytes);if (rbytes == -1){perror("Read(): ");return -1; } printf("rbuffer : %s\n",rbuffer);close(fd);return 0; }
-
練習 : 使? Linux ?件 io 接?實現 ?件復制
#include <unistd.h> #include <string.h> int main(int argc, const char *argv[]) {if(argc != 3){fprintf(stderr, "Usage : <%s> <pathname>\n", argv[0]);return -1;}int source_fd = open(argv[1], O_RDONLY);int destination_fd = open(argv[2], O_RDWR | O_CREAT);int rbytes = 0;char buffer[64] = {0};if(source_fd == -1 || destination_fd == -1){perror("open()");return -1;}while(rbytes = read(source_fd, buffer, 64)){if(rbytes == -1){perror("read()");return -1;}int wbytes = write(destination_fd, buffer, rbytes);if(wbytes == -1){perror("write()");return -1;}}close(source_fd);close(destination_fd);return 0; }
3.Linux標準io接口
3.1 標準io簡介
3.1.1 標準io與文件io
- 標準 IO 是另外?套 IO 接?,具有如下特點:
- 標準 I/O 是屬于跨平臺, 可以在 Linux、windows、mac os 上運?, ?件 IO 只能在Linux 平臺運?
- 標準 I/O ?帶緩沖區,有更?的 IO 效率
- 標準 IO 提供豐富的操作?本信息接?
- 標準 IO 底層需要依賴于 ?件 IO
- 在 Linux 系統下, 標準 I/O 是屬于 glibc 庫的?部分
3.1.2 流與FILE對象
-
流(stream):流是?串連續不斷的傳輸的數據的集合,就像?管??的?流,在?管的?端?點?點地供?,?在?管的另?端看到的是?股連續不斷的?流。
-
?般流可以分為 ?本流 與 ?進制流
- ?本流:
- 在流中處理的數據是以字符出現。
- 在?本流中,’\n’被轉換成回?符 CR 和換?符 LF的 ASCII 碼 0DH 和 0AH, ?當輸出時,0DH 和 0AH 被轉換成’\n’。
- 二進制流:
- 流中處理的是?進制序列。
- 若流中有字符,則??個字節的?進制 ASCII 碼表示;若是數字,則?對應的?進制數表示
- ?本流:
-
?件指針:
-
FILE 指針:每個被使?的?件都在內存中開辟?個區域,?來存放?件的有關信息,這些信息是保存在?個結構體類型的變量中,該結構體類型是由系統定義的,取名為 FILE。
-
FILE 結構體定義在 /usr/libio.h 中 struct _IO_FILE
struct _IO_FILE;typedef struct _IO_FILE __FILE;
-
標準 I/O 庫的所有操作都是圍繞流 (stream) 來進?的,在標準 I/O 中,流? FILE * 來描述
-
標準 I/O 庫是由 Dennis Ritchie 在 1975 年左右編寫的
-
-
?件指針關聯到數據流的兩端, 可以抽象成 “?管”
3.2 標準輸入、標準輸出、標準錯誤輸出
3.2.1 簡介
- 標準 I/O 預定義 3 個流對象指針, 在應?程序運??動被打開.
- 標準輸? : 流對象操作的是標準輸?設備, 流對象指針的名稱為 stdin , 對應的?件描述符為 0
- 標準輸出 : 流對象操作的是標準輸出設備, 流對象指針的名稱為 stdout, 對應的?件描述符為 1
- 標準錯誤輸出: 流對象操作的是標準錯誤輸出設備, 流對象指針的名稱為 stderr, 對應的?件描述符為 2
- 對應的 printf , 函數操作的就是 stdout , 由于是默認操作, ?般?需指定具體的流對象參數
- 當在輸出時需要指定流對象的類型時, 則需要使? fprintf 函數
3.2.2 fprintf函數
-
函數原型
int fprintf(FILE *stream, const char *format, …);
-
函數功能
將格式化數據寫?到指定?件中。
-
函數參數
stream : 流對象指針 format : 格式字符串
-
示例 : 通過 stdout 與 stderr 進?輸出
int main(void) {fprintf(stdout,"Linux std io .\n");fprintf(stderr,"can't open file.\n"); while(1){}return 0; }
- 注意: 在上述程序中, 將 ‘\n’ 去掉之后, 在添加?個死循環后, 則程序運?的結果則不同, 這?是 與標準 I/O 的緩沖區有關系.
3.2.3 文件緩沖
- 文件緩沖系統
- 緩沖文件系統
- 盡量減少使? read/write 的調?次數, 來提?效率, 每次進?系統調?都會涉及到從 ?戶空間到內核空間的切換以及內核進?系統調?所產?的開銷
- 系統?動的在內存中為每?個正在使?的?件開辟?個緩沖區,從內存向磁盤輸出數據必須先 送到內存緩沖區,裝滿緩沖區在?起送到磁盤中去.
- 從磁盤中讀數據,則?次從磁盤?件將?批數據讀?到內存緩沖區中,然后再從緩沖區逐個的 將數據送到程序的數據區
- 緩沖文件系統
-
標準 I/O 的緩存??為 8192, 在系統中定義如下 (stdio.h):
#define BUFSIZE 8192
-
?般標準 I/O 的分類為:
-
全緩存 : 當相應的緩沖區已經裝滿數據時, 才進??次 I/O 操作
-
?緩存 : 當相應的緩沖區存儲??時,則進??次 I/O 操作, stdout 就是?緩存
-
不緩存 : 直接進? I/O 操作, 不進?緩存, stderr 就是不緩存
3.2.4 緩沖區強制刷新
-
?般情況下, 程序在結束時會 ?動刷新緩沖區, 但是當程序還未結束時, 刷新緩沖區則需要調? fflush() 函數
-
函數原型
int fflush(FILE *stream);
-
函數功能
強制刷新緩沖區。
-
函數參數
stream:流對象指針
-
函數返回值
成功:返回0 失敗:返回-1
-
-
示例 : 使? fflush 函數刷新緩沖區的數據
#include <stdio.h> int main(void) {printf("hello.");fflush(stdout);while(1){}return 0; }
-
練習 : 使? fprintf 函數 “Hello,Linux io” 到 標準輸出,并使? fflush 函數進?強制刷新.
#include <stdio.h>int main(int argc, const char *argv[]) {fprintf(stdout, "Hello, Linux io");fflush(stdout); while(1){}return 0; }
3.3 Linux標準io-fopen/fclose
3.3.1 fopen函數
-
函數頭文件
#include <stdio.h>
-
函數原型
FILE *fopen(const char *pathname, const char *mode);
-
函數功能
打開?件,并獲取流對象指針.
-
函數參數
- pathname : 路徑名
- mode : 打開模式
- r或rb:以只讀方式打開?件,前提是該?件必須存在
- r+或r+b:以可讀可寫方式打開?件,前提是該?件必須存在
- w或wb:以只寫方式打開?件,若?件存在則?件?度清為 0, 即會清空?件以前內容。若?件不存在則創建該?件.
- w+或w+b或wb+:以可讀可寫方式打開?件,若?件存在則?件?度清為零,即會清空?件以前內容, 若?件不存在則創建該?件.
- a或ab:以只寫與追加的方式打開文件,若?件不存在,則會新建該?件, 如果?件存在,寫?的數據會被加到?件尾,即?件原先的內容會被保留。
-
函數返回值
- 成功:返回?件指針
- 失敗:返回 NULL, 并設置 errno
3.3.2 fclose函數
-
函數頭文件
#include <stdio.h>
-
函數原型
int fclose(FILE *stream);
-
函數功能
關閉已經打開的?件.
-
函數參數
stream : ?件指針
-
示例 : 以讀寫?式打開?件 test.txt,如果該?件不存在,則創建. 如果該?件已經存在,則?度截短為 0.
int main(int argc, const char *argv[]) {if(argc != 2){fprintf(stderr, "usage <%s> <pathname>\n", argv[1]);return -1;}FILE* fd = NULL;fd = fopen(argv[1], "w+");if(fd == NULL){fprintf(stderr, "fopen()\n");return -1;}return 0; }
3.4 Linux標準io-fgetc/fputc
3.4.1 fgetc函數
-
函數頭文件
#include <stdio.h>
-
函數功能
從文件中讀取一個字符。
-
函數原型
int fgetc(FILE* stream);
-
函數參數
stream:文件指針
-
函數返回值
- 成功:返回所讀到字符的ASCII碼
- 失敗:返回EOF
3.4.2 fputc函數
-
函數頭文件
#include <stdio.h>
-
函數功能
輸出一個字符到文件中;
-
函數原型
int fputc(int c, FILE* stream);
-
函數參數
c:待寫?的字符 stream:文件指針
-
函數返回值
- 成功:返回寫入字符的ASCII碼
- 失敗:返回EOF,并設置errno
-
示例 : 實現 cat 命令功能, 將?件中的數據顯示到 stdout 上.
#include <stdio.h> #include <stdlib.h> int main(int argc,char *argv[]) {FILE *fp = NULL;if (argc != 2){ fprintf(stderr,"Usage : ./a.out <filename>\n"); exit(-1);} fp = fopen(argv[1],"r");if (fp == NULL){ fprintf(stderr,"fopen failed.\n"); exit(-1);} char ch; for (;;){ch = fgetc(fp);if (ch == EOF)break;fputc(ch,stdout);}fclose(fp);return 0; }
練習: 使? fgetc 與 fputc 實現?件復制功能
#include <stdio.h>int main(int argc, const char *argv[]) {FILE *src_fd = NULL, *des_fd = NULL;char ch;if(argc != 3){fprintf(stderr, "usage <%s> <pathname>\n", argv[0]);return -1;}src_fd = fopen(argv[1], "r");des_fd = fopen(argv[2], "w+");if(src_fd == NULL || des_fd == NULL){fprintf(stderr, "fopen()\n");return -1;}while((ch = fgetc(src_fd)) != EOF){fputc(ch, des_fd);}fclose(src_fd);fclose(des_fd);return 0; }
3.5 Linux標準io-fgets/fputs
3.5.1 fgets函數
-
函數頭文件
#include <stdio.h>
-
函數功能
從文件中讀取一行數據到緩沖區中。
-
函數原型
char *fgets(char *s, int size, FILE *stream);
-
函數參數
s : 緩沖區地址 size : 最?可讀取?? stream : ?件指針
-
函數返回值
成功 : 返回緩沖區的地址, 當讀到?件尾時,會返回 NULL 失敗 : 返回 NULL
-
使?注意點
- 會將’\n’ 存儲到 buffer 中
- 會?動在 buffer 的末尾添加’\0’
- 如果???于 size, 讀取到??就返回
- 如果 size ?于??, 讀取 size 返回
3.5.2 fputs函數
-
函數頭文件
#include <stdio.h>
-
函數功能
將一行文本數據寫入到文件中。
-
函數原型
int fputs(const char *s, FILE *stream);
-
函數參數
s:緩沖區地址 stream:文件指針
-
函數返回值
成功:返回?個?負數 失敗:返回 EOF
-
示例:使? fgets 與 fputs 輸出?件內容到 stdout 上
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(int argc,char *argv[]) {FILE *fp = NULL;char buffer[64];fp = fopen(argv[1],"r");if (fp == NULL){ fprintf(stderr,"cat't open file");exit(-1);} while(fgets(buffer,64,fp) != NULL){ fputs(buffer,stdout); } fclose(fp);return 0; }
-
練習:使? fputs 與 fgets 來復制?件
#include <stdio.h> #include <stdlib.h>int main(int argc, const char *argv[]) {FILE *src_fd = NULL, *des_fd = NULL;char buffer[64] = {0};int buffer_size = sizeof(buffer);if(argc != 3){fprintf(stderr, "Usage <%s> <pathname>\n", argv[0]);return -1;}src_fd = fopen(argv[1], "r");des_fd = fopen(argv[2], "w+");if(src_fd == NULL || des_fd == NULL){fprintf(stderr, "fopen()\n");exit(-1);}while((fgets(buffer, buffer_size, src_fd)) != NULL){fputs(buffer, des_fd);}fclose(src_fd);fclose(des_fd);return 0; }
3.6 Linux標準io-格式化輸入輸出與時間獲取
當遇到典型的格式化數據進?處理時, 就需要相應?于格式化輸? / 輸出的函數來完成, ?如?期就是典型的具有格式的數據
?期數據 : 2022 年 10 ? 22 ?
地址數據 : 湖北省武漢市…
3.6.1 格式化輸出函數
-
printf函數
-
函數頭文件
#include <stdio.h>
-
函數功能
輸出信息到標準輸出。
-
函數原型
int printf(const char *format, …);
-
函數返回值
實際輸出的字節數。
-
-
fprintf函數
-
函數頭文件
#include <stdio.h>
-
函數功能
將格式化數據輸出到文件。
-
函數原型
int fprintf(FILE *stream, const char *format, …);
-
函數參數
stream : 流對象指針 format : 格式字符串
-
函數返回值
實際輸出的字節數。
-
-
sprintf函數
-
函數頭文件
#include <stdio.h>
-
函數功能
將格式化數據輸出到字符串緩沖區中。
-
函數原型
int sprintf(char *str, const char *format, …);
-
函數參數
str : 字符串緩沖區地址 format : 格式字符串地址
-
函數返回值
實際輸出的字節數。
-
3.6.2 格式化輸入函數
-
scanf函數
-
函數頭文件
#include <stdio.h>
-
函數功能
從標準輸?讀取格式化數據到緩沖區中。
-
函數原型
int scanf(const char *format, …);
-
函數參數
format : 格式字符串地址
-
函數返回值
實際讀取的字節數。
-
-
fscanf函數
-
函數頭文件
#include <stdio.h>
-
函數功能
從?件中讀取格式化數據。
-
函數原型
int fscanf(FILE *stream, const char *format, …);
-
函數參數
stream : 流對象指針 format : 格式字符串地址
-
函數返回值
實際讀取的字節數。
-
例:格式化輸出
#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <string.h> int main(int argc,char *argv[]) {FILE *fp = NULL; fp = fopen(argv[1],"w");if ( fp == NULL){ fprintf(stderr,"can't open file.\n");return -1;} int numa = 10; float numb = 1.23456;char *str = "Hello";char buffer[64];fprintf(fp,"%d-%f-%s",numa,numb,str);sprintf(buffer,"%d-%f-%s",numa,numb,str);puts(buffer);fclose(fp);return 0; }
-
-
sscanf函數
-
函數頭文件
#include <stdio.h>
-
函數功能
從字符串讀取格式化數據。
-
函數原型
int sscanf(const char *str, const char *format, …);
-
函數參數
str : 字符串地址 format : 格式字符串地址
-
函數返回值
實際讀取的字節數。
-
例:格式化輸入
#include <stdio.h> #include <stdlib.h> int main(int argc,char *argv[]) {FILE *fp = NULL; int numa = 0,numb = 0,numc = 0; char buffer[64] = "10-20-30";if (argc != 2){ fprintf(stderr,"Usage : %s <pathname> \n",argv[0]);return -1; } fp = fopen(argv[1],"r");if (fp == NULL){perror("Error fopen(): ");return -1; } fscanf(fp,"%d-%d-%d",&numa,&numb,&numc);printf("numa = %d,numb = %d,numc = %d\n",numa,numb,numc);numa = 0,numb = 0,numc = 0;sscanf(buffer,"%d-%d-%d",&numa,&numb,&numc);printf("numa = %d,numb = %d,numc = %d\n",numa,numb,numc);fclose(fp);return 0; }
-
3.6.3 獲取系統時間
-
在 Linux 中獲取主要需要以下兩個步驟
-
Step 1 : 通過 time() 函數獲取從 1970 年?今的秒數
-
Step 2 : 通過 localtime() 或者 ctime() 函數
-
-
time函數
-
函數頭文件
#include <time.h>
-
函數功能
獲取從 1970-1-1 ?今的時間秒數 (時間戳)。
-
函數原型
time_t time(time_t *tloc);
-
函數參數
tloc:輸出參數,存儲時間變量的指針。
-
函數返回值
如果參數為空,則返回當前時間距1970年1月1日00:00點 UTC的秒數; 如果參數不為空,此時返回值和參數都為當前時間距1970年1月1日00:00點 UTC的秒數。
-
注意:
UTC就是一個全世界都用的“標準時間”。這個時間是基于非常準確的原子鐘來計算的,所以非常準確。 UTC就像一把全球統一的“尺子”,用來量時間。這樣,不論你在世界的哪個角落,只要提到UTC時間,大家都知道現在是幾點。 當然,每個地方因為日出日落的時間不同,所以會在UTC的基礎上加或者減幾個小時,形成自己的地方時間。 但UTC時間就像一個基準,幫助大家更好地理解和溝通時間。
-
-
localtime函數
-
函數頭文件
#include <time.h>
-
函數功能
將時間戳轉換成本地時間, 并存儲到 struct tm 結構體變量中。
-
函數原型
struct tm *localtime(const time_t *timep);
-
函數參數
timep:這是一個指向 time_t 類型變量的指針,該變量包含了要轉換的時間。
-
函數返回值
返回 struct tm 結構體指針
struct tm
的定義大致如下:struct tm {int tm_sec; /* Seconds (0-60) */int tm_min; /* Minutes (0-59) */int tm_hour; /* Hours (0-23) */int tm_mday; /* Day of the month (1-31) */int tm_mon; /* Month (0-11) */int tm_year; /* Year - 1900 */int tm_wday; /* Day of the week (0-6, Sunday = 0) */int tm_yday; /* Day in the year (0-365, 1 Jan = 0) */int tm_isdst; /* Daylight saving time */ };
-
獲取當前時間并轉換本地時間,以 %d-%d-%d %d::%d::%d 進?打印
#include <stdio.h> #include <time.h> int main(void) {time_t t;struct tm *p_datetime;t = time(NULL);p_datetime = localtime(&t);printf(" %d-%d-%d %d::%d::%d\n",p_datetime->tm_year + 1900,p_datetime->tm_mon + 1,p_datetime->tm_mday,p_datetime->tm_hour,p_datetime->tm_min ,p_datetime->tm_sec );return 0; }
練習 :
獲取系統時間,按照 <2022-5-8 23::15:00> 格式寫?到?件中
#include <stdio.h> #include <time.h> #include <stdlib.h> int main(int argc, const char *argv[]) {time_t sec = time(NULL);struct tm * date_time_p = localtime(&sec);FILE *fd = NULL;if(argc != 2){fprintf(stderr, "Usage <%s> <pathname>\n", argv[0]);return -1;}fd = fopen(argv[1], "w+");if(fd == NULL){fprintf(stderr, "fopen()\n");exit(-1);}fprintf(fd, "<%d-%d-%d::%d:%d:%d>\n", date_time_p->tm_year + 1900,date_time_p->tm_mon + 1,date_time_p->tm_mday,date_time_p->tm_hour,date_time_p->tm_min,date_time_p->tm_sec);return 0; }
-
3.7.Linux標準io-?進制讀寫與?件定位
3.7.1 二進制文件讀寫
在標準 I/O 中, ?于進??進制?件進?讀寫時需要調? fread 與 fwrite。
-
fread函數
-
函數頭文件
#include <stdio.h>
-
函數功能
從?進制?件中讀取數據到緩沖區
-
函數原型
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
-
函數參數
ptr : 緩沖區地址 size : 讀取每個數據塊的?? nmemb : 讀取數據對象的個數 stream : ?件指針
-
函數返回值
成功 : 返回實際讀取的數據對象的個數 失敗: 當到達?件尾或者發?錯誤,返回較?的數據對象個數或者 0
-
-
fwrite函數
-
函數頭文件
#include <stdio.h>
-
函數功能
將緩沖區中的數據寫?到?件中
-
函數原型
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
-
函數參數
ptr : 緩沖區地址 size : 寫入的每個數據塊的?? nmemb : 寫入的數據對象的個數 stream : ?件指針
-
函數返回值
成功 : 返回實際寫入的數據對象的個數 失敗: 當到達?件尾或者發?錯誤,返回較?的數據對象個數或者 0
-
示例 : 使? fwrite 存儲?個浮點數組的數據到?件中。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <time.h> #include <errno.h> int main(int argc,char * argv[]) {FILE *fp = NULL;fp = fopen(argv[1], "w+");if (fp == NULL){fprintf(stderr,"can't open file.");exit(-1);}float numbers[5] = {1.1,1.2,1.3,1.4,1.5};size_t nwbytes = 0 ;nwbytes = fwrite(numbers,sizeof(float),5,fp);if (nwbytes != 5){fprintf(stderr,"fwrite():%s\n",strerror(errno));exit(-1);}rewind(fp);size_t nrbytes = 0;float rnumbers[5] = {0.0};nrbytes = fread(rnumbers,sizeof(float),5,fp);if (nrbytes != 5){fprintf(stderr,"fread():%s\n",strerror(errno));exit(-1);}int i;for (i = 0;i < 5;i++){printf(" %f ",rnumbers[i]);}putchar('\n');fclose(fp);return 0; }
-
3.7.2 文件定位
在對?件流進?操作時, ?般情況下都是順序操作, 但如果定位到?件流某?個地?進?操作,則需要使? fseek 函數進??件流的定位。
-
fseek函數
-
函數頭文件
#include <stdio.h>
-
函數功能
對?件進?定位
-
函數原型
int fseek(FILE *stream, long offset, int whence);
-
函數參數
- stream : ?件指針
- offset : 偏移量
- whence: 偏移相對位置
- SEEK_SET :相對于?件頭
- SEEK_CUR : 相對于?件當前位置
- SEEK_END : 相對于?件尾
-
函數返回值
成功: 返回設置后的偏移位置 失敗:返回 -1, 并設置 errno
-
示例 : 使? fseek 函數進?數據流的定位
int main(int argc,char * argv[]) {FILE *fp = NULL;char temp;fp = fopen(argv[1],"r");if (fp == NULL){fprintf(stderr,"open failed.");return -1;}fseek(fp,5,SEEK_SET);temp = fgetc(fp);printf("temp = %c\n",temp);fclose(fp);return 0; }
-
練習 : 使??進制操作接? fread 與 fwrite 復制?張圖?
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #define BUFFER_SIZE 1024 int main(int argc, const char *argv[]) {FILE * src_fd = NULL, * des_fd = NULL;char buffer[BUFFER_SIZE] = {0};int nrbytes = 0, nwbytes = 0;if(argc != 3){fprintf(stderr, "Usage <%s> <pathname>\n", argv[0]);exit(-1);}src_fd = fopen(argv[1], "r");des_fd = fopen(argv[2], "w+");if(src_fd == NULL || des_fd == NULL){fprintf(stderr, "fopen():%s\n", strerror(errno));exit(-1);}while((nrbytes = fread(buffer, 1, BUFFER_SIZE, src_fd)) > 0){fwrite(buffer, 1, nrbytes, des_fd);}fclose(src_fd);fclose(des_fd);return 0; }
-
4.靜態庫與動態庫的原理與制作
4.1 程序的編譯過程
-
程序在編譯時分為多個階段
-
預處理
- 處理所有預處理命名,包括宏定義、條件編譯指令、?件包含指令;
-
編譯
- 進?詞法分析、語法分析、語義分析后,將代碼翻譯成匯編指令;
-
匯編
- 將匯編指令翻譯成機器指令,也就是?進制,形成?標?件;
-
鏈接
- 將多個?標?件進?鏈接,得到?個程序最后的執??件。
-
-
編譯過程演示示例,假定源?件名為 hello.c
-
Step 1:預處理
gcc -E hello.c -o hello.i
-
Step 2:編譯
gcc -S hello.i -o hello.S
-
Step 3:匯編
gcc -c hello.S -o hello.o
-
Step 4:鏈接
gcc hello.o -o hello
-
4.2 靜態庫
4.2.1 函數庫
- 函數庫是實現了某?類功能的若?個函數的集合。
- 函數庫可以編譯獨?的?進制?件,在進?步制作成靜態庫與動態庫進?鏈接使?。
4.2.2 靜態庫的文件格式
- 靜態庫是函數庫?進制?件的?種形式, 在 windows 與 Linux 下對應的?件類型。
- Windows:name.lib
- Linux:libname.a
4.2.3 靜態庫的特點
- 當編譯器鏈接靜態庫的時候,如果在可執??件中有調?靜態庫的函數接?,則會將靜態庫拷?到可執??件中;
- 由于可執??件中有靜態庫中函數接?的實現代碼,運?的時候不需要靜態庫;
- 由于鏈接的時候,需要將庫函數接?實現代碼拷?到可執??件中,所以?成。
4.2.4 靜態庫的制作
-
靜態庫的制作
-
將file.c編譯成file.o
gcc -c file.c -o file.o
-
將file.o生成靜態庫
ar -rs libfile.a file.o
-
ar 命令?于制作靜態庫的命令, 可以使??些常?的選項
-s:將?標?件的索引符號添加到庫中; -r : 在庫中更新?件或者添加新的?件。
-
4.2.5 靜態庫的鏈接
-
靜態庫在鏈接使?時需要指定頭?件的位置與靜態庫的位置
- -I:指定頭?件
- -L:指定庫的位置
- -l:指定鏈接的庫的名字
gcc -I <頭?件路徑> -L < 庫的路徑 > -l < 靜態庫的名字 > -o < 可執??件名 >
-
gcc 編譯器默認搜索頭?件與庫?件的路徑
/usr/include 為頭?件默認路徑 /usr/lib 與 /lib 為庫的默認路徑
-
靜態庫的優點與缺點
- 優點
- 可執?程序在執?的時候,不需要加載動態庫,可以直接運?;
- 缺點
- 多個程序鏈接靜態庫的時候,需要拷?多份靜態庫的代碼,占?的內存較多;
- 優點
-
練習 : 設計?個?于進?算術運算的庫 add.h 與 add.c ,然后制作成靜態庫進?鏈接
-
第一步:寫程序
add.c
int add(int a, int b) {return a + b; }
add.h
#ifndef __ADD_H__ #define __ADD_H__ extern int add(int a, int b); #endif
main.c
#include <stdio.h> #include "add.h" int main(int argc, const char *argv[]) {printf("%d\n", add(10, 20));return 0; }
-
第二步:寫命令
gcc -c add.c -o add.o ar -rs libadd.a add.o gcc -I . -L . main.c -l add -o exec
-
4.3 動態庫
4.3.1 動態庫的文件格式
- 動態庫的文件格式如下:
- Windows:name.dll
- Linux:libname.so
4.3.2 動態庫的特點
- 當編譯器鏈接動態庫的時候,會在可執??件的頭信息中記錄庫的名字,便于在操作系統執?這個可執行文件時, 讓操作系統去加載對應的動態庫。
- 當操作系統執?可執??件時候,會先讀取可執??件的頭信息,然后加載頭信息中記錄的動態庫到內存中運?。
- 當可執??件調?動態庫中的函數時,則需要加載動態庫到內存中。
4.3.3 動態庫的制作
-
動態庫的制作過程如下:
-
將 file.c 編譯成 file.o
gcc -c file.c -o file.o
-
將file.o生成動態庫
gcc -shared file.o -o libfile.so
-
4.3.4 動態庫的鏈接
-
動態庫庫在鏈接使?時需要指定頭?件的位置與動態庫庫的位置
-
-I:指定頭?件
-
-L:指定庫的位置
-
-l:指定鏈接的庫的名字
gcc -I <頭?件路徑> -L < 庫的路徑 > -l < 動態庫的名字 > < 源?件 >-o < 可執??件名 >
-
-
gcc 編譯器默認搜索頭?件與庫?件的路徑
-
/usr/include 為頭?件默認路徑
-
/usr/lib 與 /lib 為庫的默認路徑
-
-
與靜態庫不同的是 動態庫在鏈接成功之后,還需要加載動態庫到內存中, 編譯與加載是兩個不同的動作
-
編譯器在編譯時并沒有將動態庫中的函數拷?到可執?程序中,只是記錄動態庫的名字;
-
在程序運?中調?到動態苦衷的函數時時,則需要將動態庫加載到內存中;
-
動態庫默認加載動態庫的路徑與鏈接動態庫的默認路徑是相同的,都是 /lib 與 /usr/lib;
-
當默認路徑下沒有時,則會到 LD_LIBRARY_PATH 環境變量下去找;
-
可以通過 LD_LIBRARY_PATH 來設置動態庫的路徑。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH: ??庫所在的路徑
-
-
練習: 將前?次練習對應的 算術運算庫 (add.h/add.c) 制作成動態庫進?鏈接, 并測試
gcc -c add.c -o add.o gcc -I . -L . main.c -l add -o exec export LD_LIBRARY_PATH=. ./exec
5.項目-實現一個基礎的shell程序
5.1 項目簡介與框架設計
5.1.1 項目簡介
- 實現?個基礎的 shell 程序,主要完成兩個命令的功能 cp 和 ls
- cp 命令主要實現:
- ?件復制
- ?錄復制
- ls 命令主要實現:
- ls -l 命令的功能
- cp 命令主要實現:
5.1.2 項目框架設計
-
在框架設計上,采?模塊化設計思想,并具備?定的可擴展性, 具體框架如下:
-
cmd_handle 模塊: ?于解析命令相關信息,并進?命令的分發執?
-
cmd_ls 模塊 : ?于執? ls 命令
-
cmd_cp 模塊 : ?于執? cp 命令
-
cmd_xxx 模塊 : ?于擴展
-
5.1.3 基本框架搭建
-
step1:根據框架規劃創建相關源文件
模塊 源文件 命令處理中?模塊 cmd_handle.c、cmd_handle.h ls 命令模塊 cmd_ls.c、cmd_ls.h cp 命令模塊 cmd_cp.c、cmd_cp.h ?程管理 Makefile 主函數 main.c -
step2:創建Makefile用于管理工程
OBJS := main.o cmd_ls.o cmd_cp.o cmd_handle.o TARGET := tinyshell $(TARGET): $(OBJS) @gcc $^ -o $@ @echo "Done." %.o:%.c @gcc -c $< -o $@ clean: rm -rf *.o $(TARGET)
-
:= 表示當前位置所賦的值,?不是整個 Makefile 展開之后的值,= 是整個 Makefile 展開之后的所賦的值
-
= 號示例
x = fooy = $(x) barx = xyz
- 上述示例中的 y 的值為 xyz bar
-
:= 號示例
x := fooy := $(x) barx := xyz
- 上述示例中的 y 的值為 foo bar
-
-
$(TARGET) : 表示獲取 TARGET 變量的值
-
%.o : %.c :
- % 表示通配符
- %.o : ?于匹配任意 .o ?件, 如 cmd_handle.o ,cmd_ls.o ,…
- %.c : ?于匹配任意 .c ?件, 如 cmd_handle.c ,cmd_ls.c ,…
-
-
step3:在main.c編寫基本的main函數
int main() {return 0; }
-
step4:編譯測試
在命令?輸? make 命令進?測試, 顯示 Done , 則表示編譯通過
5.1.4 項目主循環實現
-
項?的主循環主要完成的功能:
-
step1:循環獲取?戶輸? main.c
#include <stdio.h> #include <string.h>#define SZ_CMD 64int main(void) {char command[SZ_CMD] = {0}; for(;;){printf("TinyShell > ");fgets(command,SZ_CMD,stdin); command[strlen(command) - 1] = '\0'; if (strncmp(command,"quit",4) == 0){ printf("GoodBye\n");break;}cmd_execute(command); } return 0; }
-
step 2 : 調? cmd_handle 的 cmd_execute 接?執?相應的命令 cmd_handle.h cmd_handle.c
cmd_handle.h
#ifndef __CMD_HANDLE_H_ #define __CMD_HANDLE_H_ #define DEBUG extern int cmd_execute(char *cmd_str); #endif
cmd_handle.c
int cmd_execute( char *cmd_str) { #ifdef DEBUGprintf("[DEBUG] : cmd string : < %s >\n",cmd_str); #endifreturn 0; }
-
step 3 : 編譯并執??程
-
5.2 命令處理框架設計——解析命令
5.2.1 解析命令與分發命令基本思路
- 輸?的命令是?個完整字符串,?如復制 “cp test.txt test1.txt” , 在實際實現業務邏輯時需要進?拆分
- 具體在解析字符串的步驟如下:
- step 1 : 設計?定義的數據結構存儲拆分之后的命令信息
- step 2 : 使? strtok 函數對命令字符串進?拆分, 并存儲到?定義數據結構中
- step 3 : 按照命令名字分發到具體模塊中執?
5.2.2 自定義數據結構設計
-
數據結構定義
-
對于解析之后的字符串,需要保存到?定義的數據結構中
- 命令名稱
- 參數個數
- 參數列表
-
具體的數據結構設計如下:
#define SZ_NAME 8 // 命令名稱的最大長度 #define SZ_ARG 32 // 每個參數的最大長度 #define SZ_COUNT 2 // 參數最大個數 #include <stdio.h> #include <string.h> #define DEBUG typedef struct command {char cmd_name[SZ_NAME]; // 命令名稱char cmd_arg_list[SZ_COUNT][SZ_ARG]; // 參數int cmd_arg_count; // 參數個數 }cmd_t;
-
-
數據結構初始化
-
數據結構初始化 調? init_command_struct 函數, 具體實現如下:
void init_command_struct(cmd_t *pcmd) { int i; memset(pcmd->cmd_name,0,SZ_NAME); /*頭文件:#include <string.h>函數原型:void *memset(void *str, int c, size_t n) 功能:用于將一段內存區域設置為指定的值。memset() 函數將指定的值 c 復制到 str 所指向的內存區域的前 n 個字節中,*/for (i = 0;i < SZ_COUNT;i++){ memset(pcmd->cmd_arg_list[i],0,SZ_ARG); } pcmd->cmd_arg_count = 0; }
-
-
數據結構調試打印
-
命令數據結構的調試打印 調? print_command_info 函數,具體實現如下:
void print_command_info(cmd_t *pcmd) {int i;printf("==================\n");printf("[DEBUG] cmd name : < %s >\n",pcmd->cmd_name);printf("[DEBUG] cmd arg count : < %d >\n",pcmd->cmd_arg_count);printf("[DEBUG] cmd arg list : ");for (i = 0;i < pcmd->cmd_arg_count;i++){printf(" %s ",pcmd->cmd_arg_list[i]);}printf("\n==================\n"); }
-
-
數據結構初始化與測試
-
在 cmd_execute 函數中,定義命令數據結構,并進?初始化后,并進?調試
int cmd_execute(char *cmd_str) {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); #ifdef DEBUG print_command_info(&command); #endifreturn 0; }
-
5.2.3 命令解析
-
字符串拆分函數strtok
-
命令的解析需要調?字符串處理函數 strtok 進?拆分
-
strtok 函數具體信息如下:
-
函數頭文件
#include <string.h>
-
函數原型
char *strtok(char *str, const char *delim);
-
函數功能
根據指定的分割字符串進?分割
-
函數參數
str : 分割字符串的地址 delim : 分割符
-
函數返回值
成功 : 返回分割后字符串?地址 失敗 : 返回 NULL
-
-
函數注意事項:
第?次調?時,需要指定字符串的地址 第?次調?時, 第?個參數可以填 NULL
-
strtok函數示例:
#include <stdio.h> #include <string.h> int main(void) {char str[] = "ABC 123 XYZ";char *first = NULL;char *other = NULL;first = strtok(str," ");printf(" first : %s\n",first);while((other = strtok(NULL," "))){printf(" other : %s\n",other);} return 0; }/* 輸出結果為 :ABC 123 XYZ */
命令字符串通過 strtok 函數進?拆分后需要存儲到?定義的數據結構
-
-
命令參數分拆與存儲
-
命令字符串通過 strtok 函數進?拆分后需要存儲到?定義的數據結構
int cmd_parse(char *cmd_str,cmd_t *pcmd) {char *p_cmd_name = NULL;char *p_cmd_arg = NULL;int index = 0;if (cmd_str == NULL || pcmd == NULL)return -1;p_cmd_name = strtok(cmd_str," "); #ifdef DEBUGprintf("[DEBUG]: cmd_name : %s\n",p_cmd_name); #endifstrcpy(pcmd->cmd_name,p_cmd_name); for(;;){p_cmd_arg = strtok(NULL," "); if (p_cmd_arg == NULL)break;strcpy(pcmd->cmd_arg_list[index++],p_cmd_arg); }pcmd->cmd_arg_count = index; #ifdef DEBUGprint_command_info(pcmd); #endifreturn 0; }
-
在實現了 cmd_parse 函數后,在 cmd_execute 函數中進?調?, 具體如下:
int cmd_execute(char *cmd_str) {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); ret = cmd_parse(cmd_str,&command); if (ret == -1)return -1; #ifdef DEBUG print_command_info(&command); #endifreturn 0; }
-
5.2.4 分發執行
-
當命令?解析完成之后,則需要進?具體分發到各個模塊具體執?, 這?調? cmd_dispatch函數, 具體實現如下:
int cmd_dispatch(cmd_t *pcmd) {if (pcmd == NULL)return -1;if (strcmp(pcmd->cmd_name,"ls") == 0){}else if (strcmp(pcmd->cmd_name,"cp") == 0){}return 0; }
-
在 cmd_execute 函數中調? cmd_dispatch 函數
int cmd_execute( char *cmd_str) {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); ret = cmd_parse(cmd_str,&command); if (ret == -1)return -1; #ifdef DEBUG print_command_info(&command); #endifret = cmd_dispatch(&command);if (ret == -1)return -1;return 0; }
5.3 CP命令設計與實現
5.2.1 需求分析
-
完成?個?錄的復制,具體要求如下:
-
實現?件復制
cp 1.txt 2.txt
-
實現?錄復制
cp src_dir dest_dir
-
5.2.2 思路分析
-
總體思路
- 根據?件類型進?判斷,如果是普通?件,則直接進?復制, 如果是?錄,則遞歸復制?錄。
-
基本思路如下:
- 判斷?件類型
- 是普通?件, 則直接進?復制
- 是?錄,則遞歸進??錄復制
- 復制目錄
- 在?標路徑創建新的同名?錄
- 打開?錄
- 遍歷?錄
- 獲取?件名,并合成源?錄絕對路徑以及?標?錄絕對路徑
- 根據路徑判斷源?件類型
- 是?件,則直接進?復制
- 是?錄,則繼續進?遞歸復制
- 判斷?件類型
5.2.3 框架設計
- 命令執行接口設計
- cp 的命令的總的??函數為 cmd_cp_execute 函數, 具體邏輯如下:
- 文件信息數據結構定義
- 復制文件的相關信息的結構體定義如下:
)
return -1;
p_cmd_name = strtok(cmd_str," “);
#ifdef DEBUG
printf(”[DEBUG]: cmd_name : %s\n",p_cmd_name);
#endif
strcpy(pcmd->cmd_name,p_cmd_name);
for(;😉
{
p_cmd_arg = strtok(NULL," ");
if (p_cmd_arg == NULL)
break;
strcpy(pcmd->cmd_arg_list[index++],p_cmd_arg);
}
pcmd->cmd_arg_count = index;
#ifdef DEBUG
print_command_info(pcmd);
#endif
return 0;
}
```
-
在實現了 cmd_parse 函數后,在 cmd_execute 函數中進?調?, 具體如下:
int cmd_execute(char *cmd_str) {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); ret = cmd_parse(cmd_str,&command); if (ret == -1)return -1; #ifdef DEBUG print_command_info(&command); #endifreturn 0; }
5.2.4 分發執行
-
當命令?解析完成之后,則需要進?具體分發到各個模塊具體執?, 這?調? cmd_dispatch函數, 具體實現如下:
int cmd_dispatch(cmd_t *pcmd) {if (pcmd == NULL)return -1;if (strcmp(pcmd->cmd_name,"ls") == 0){}else if (strcmp(pcmd->cmd_name,"cp") == 0){}return 0; }
-
在 cmd_execute 函數中調? cmd_dispatch 函數
int cmd_execute( char *cmd_str) {cmd_t command ;int ret;if (cmd_str == NULL)return -1;init_command_struct(&command); ret = cmd_parse(cmd_str,&command); if (ret == -1)return -1; #ifdef DEBUG print_command_info(&command); #endifret = cmd_dispatch(&command);if (ret == -1)return -1;return 0; }
5.3 CP命令設計與實現
5.2.1 需求分析
-
完成?個?錄的復制,具體要求如下:
-
實現?件復制
cp 1.txt 2.txt
-
實現?錄復制
cp src_dir dest_dir
-
5.2.2 思路分析
-
總體思路
- 根據?件類型進?判斷,如果是普通?件,則直接進?復制, 如果是?錄,則遞歸復制?錄。
-
基本思路如下:
- 判斷?件類型
- 是普通?件, 則直接進?復制
- 是?錄,則遞歸進??錄復制
- 復制目錄
- 在?標路徑創建新的同名?錄
- 打開?錄
- 遍歷?錄
- 獲取?件名,并合成源?錄絕對路徑以及?標?錄絕對路徑
- 根據路徑判斷源?件類型
- 是?件,則直接進?復制
- 是?錄,則繼續進?遞歸復制
- 判斷?件類型
5.2.3 框架設計
- 命令執行接口設計
- cp 的命令的總的??函數為 cmd_cp_execute 函數, 具體邏輯如下:
[外鏈圖片轉存中…(img-SOp0sRMG-1715490401070)]
- 文件信息數據結構定義
- 復制文件的相關信息的結構體定義如下: