歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

Linux cat 命令源碼剖析

最近在讀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

Copyright © Linux教程網 All Rights Reserved