最近在讀APUE, 邊看還得邊做才有效果. 正好Linux下很多命令的是開源的, 可以直接看源碼. GNU coreutils 是個不錯的選擇. 源碼包有我們最常用的 ls, cat等命令的源碼, 每個命令都比較短小精悍, 適合閱讀. 下面是我閱讀 cat 命令的一點筆記.
到這裡下載源碼. 在源碼根目錄下 ./configure; make 就可以直接編譯, 修改後make就可以編譯了. 命令源碼在 src/目錄中, lib/目錄下有一些用到的輔助函數和常量定義.
1. 命令行解析
基本上所有的Linux命令都是用getopt函數來解析命令行參數的, cat也不例外, cat使用的是getopt_long函數, 以便解析長參數, 用一些bool變量來存儲選項值. 沒什麼好說的.
2. 檢測輸入輸出文件是否相同
例如 cat test.txt > test.txt 的情況, 輸入輸出文件相同, 這是不合法的.
cat 的輸入流由命令行給定, 默認是標准輸入(stdin), 輸出流是標准輸出(stdout). 所以用字符串比較的方法是無法判斷輸入輸出是否是相同. 另外對於一些特殊的文件, 如tty, 我們是允許其輸入輸出相同的, 如 cat /dev/tty > /dev/tty 是合法的. cat采取的方式是對與regular file, 檢測設備編號和i-node是否相同. 忽略對非regular file的檢測. 這部分的代碼如下:
獲得文件屬性.
if (fstat (STDOUT_FILENO, &stat_buf) < 0)
error (EXIT_FAILURE, errno, _("standard output"));
提取文件設備編號和i-node. 對於非 regular 類型的文件, 忽視檢測.
if (S_ISREG (stat_buf.st_mode))
{
out_dev = stat_buf.st_dev;
out_ino = stat_buf.st_ino;
}
else
{
check_redirection = false;
}
進行檢查. check_redirection為false就不檢查.
if (fstat (input_desc, &stat_buf) < 0)<span > </span>// input_desc為輸入文件描述符
{
error (0, errno, "%s", infile);
ok = false;
goto contin;
}
if (check_redirection
&& stat_buf.st_dev == out_dev && stat_buf.st_ino == out_ino
&& (input_desc != STDIN_FILENO))
{
error (0, 0, _("%s: input file is output file"), infile);
ok = false;
goto contin;
}
Tips: '-' 表示的是標准輸入, 如 cat - 命令實際是從標准輸入讀取字節. 所以cat可以配合管道命令這樣用: echo abcd | cat file1 - file2. 只輸入 cat 命令默認就是從標准輸入讀取字節.
3. 一次讀寫的字節數目
cat是以read, write函數為基礎實現的, 一次讀寫的字節數的多少也影響了程序的性能.
insize 和 outsize 變量分別表示一次讀和寫的字節數目.
insize = io_blksize (stat_buf);
enum { IO_BUFSIZE = 128*1024 };
static inline size_t
io_blksize (struct stat sb)
{
return MAX (IO_BUFSIZE, ST_BLKSIZE (sb));<span > </span>/* ST_BLKSIZE( )宏的值視系統而定, 在lib/stat-size.h中定義 */
}
outsize值的設定類似insize.
4. simple_cat
如 cat 命令不使用任何格式參數, 如 -v, -t. 那麼就調用simple_cat來完成操作, simple_cat的優點是速度快, 因為它在某些系統上有可能是以二進制方式讀寫文件. 參考 man 3 freopen.
if (! (number || show_ends || squeeze_blank))
{
file_open_mode |= O_BINARY;<span > </span>/* 在linux下O_BINARY為0, 沒有任何效果, 但有些系統是表示二進制形式打開文件 */
if (O_BINARY && ! isatty (STDOUT_FILENO))
/* 調用 freopen, 包含錯誤處理, 將輸出流的mode改為"wb" */
xfreopen (NULL, "wb", stdout);
}
無任何格式參數, 則調用simple_cat
if (! (number || show_ends || show_nonprinting
|| show_tabs || squeeze_blank))
{
insize = MAX (insize, outsize);
/* xzz 分配內存, 失敗則調用 xmalloc-die() 終止程序並報告錯誤 */
inbuf = xmalloc (insize + page_size - 1);
ok &= simple_cat (<strong>ptr_align</strong> (inbuf, page_size), insize);
}
ptr_align是一個輔助函數. 因為IO操作一次讀取一頁, ptr_align是使得緩沖數組的起始地址為也大小的整數倍, 以增加IO的效率.
static inline void *
ptr_align (void const *ptr, size_t alignment)
{
char const *p0 = ptr;
char const *p1 = p0 + alignment - 1;
return (void *) (p1 - (size_t) p1 % alignment);
}
simple_cat函數很簡單
static bool
simple_cat (
/* Pointer to the buffer, used by reads and writes. */
char *buf,
/* Number of characters preferably read or written by each read and write
call. */
size_t bufsize)
{
/* Actual number of characters read, and therefore written. */
size_t n_read;
/* Loop until the end of the file. */
while (true)
{
/* Read a block of input. */
/* 普通的read可能被信號中斷 */
n_read = safe_read (input_desc, buf, bufsize);
if (n_read == SAFE_READ_ERROR)
{
error (0, errno, "%s", infile);
return false;
}
/* End of this file? */
if (n_read == 0)
return true;
/* Write this block out. */
{
/* The following is ok, since we know that 0 < n_read. */
size_t n = n_read;
/* full_write 和 safe_read都調用的是 safe_sw, 用宏實現的,
* 查看 safe_write.c 就可以發現其實現的關鍵.
*/
if (full_write (STDOUT_FILENO, buf, n) != n)
error (EXIT_FAILURE, errno, _("write error"));
}
}
}
更多詳情見請繼續閱讀下一頁的精彩內容: http://www.linuxidc.com/Linux/2014-11/109104p2.htm