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 style="white-space:pre">	</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
          && (<strong>input_desc != STDIN_FILENO</strong>))<span style="white-space:pre">		</span>/*  若输入为标准输入或其重定向, 也不检測, 所以命令 cat <test >test 也是合法的 */
        {
          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 style="white-space:pre">		</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 style="white-space:pre">		</span>/* 在linux下O_BINARY为0, 没有不论什么效果, 但有些系统是表示二进制形式打开文件 */
      if (O_BINARY && ! isatty (STDOUT_FILENO))
<span style="white-space:pre">	</span>/* 调用 freopen, 包括错误处理, 将输出流的mode改为"wb" */
        xfreopen (NULL, "wb", stdout);
    }

无不论什么格式參数, 则调用simple_cat

 if (! (number || show_ends || show_nonprinting
             || show_tabs || squeeze_blank))
        {
          insize = MAX (insize, outsize);
<span style="white-space:pre">	</span> /* 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函数非常easy

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"));
      }
    }
}


5. safe_rw, full_rw 函数

read 和write函数在读写第一个字符之前有可能被signal中断, safe_rw能够恢复被中断的read和write过程. 这个函数很tricky, 它的名字safe_rw和rw事实上都是宏定义, 条件编译能够将此函数编译成safe_read, safe_write两个函数.

<strong>size_t </strong>/* 原始的read()函数返回值是 ssize_t */
safe_rw (int fd, void const *buf, size_t count)
{
  /* Work around a bug in Tru64 5.1.  Attempting to read more than
     INT_MAX bytes fails with errno == EINVAL.  See
     <http://lists.gnu.org/archive/html/bug-gnu-utils/2002-04/msg00010.html>.
     When decreasing COUNT, keep it block-aligned.  */
  enum { BUGGY_READ_MAXIMUM = INT_MAX & ~8191 };

  for (;;)
    {
      ssize_t result = rw (fd, buf, count);

      if (0 <= result)
        return result;
  <strong>    else if (IS_EINTR (errno))<span style="white-space:pre">	</span>/* signal 中断 */
        continue;</strong>
      else if (errno == EINVAL && BUGGY_READ_MAXIMUM < count)
        count = BUGGY_READ_MAXIMUM;
      else<span style="white-space:pre">		</span>/* 返回 (size_t) -1 */
        return result;
    }
}


read, write 读写过程中有可能被 signal 中断, full_rw 能够恢复读写过程, 直到读写到了指定数目的字节或到达文件结尾(EOF), 或者是读写出错. 返回当前已经读写了的字节数目. full_rw() 函数名也是宏定义的, 实际上实现了full_read() full_write().

/* Write(read) COUNT bytes at BUF to(from) descriptor FD, retrying if
   interrupted or if a partial write(read) occurs.  Return the number
   of bytes transferred.
   When writing, set errno if fewer than COUNT bytes are written.
   When reading, if fewer than COUNT bytes are read, you must examine
   errno to distinguish failure from EOF (errno == 0).  */
size_t
full_rw (int fd, const void *buf, size_t count)
{
  size_t total = 0;
  const char *ptr = (const char *) buf;

  while (count > 0)
    {
      size_t n_rw = safe_rw (fd, ptr, count);
      if (n_rw == (size_t) -1)<span style="white-space:pre">	</span>/* error */
        break;
      if (n_rw == 0)<span style="white-space:pre">	</span>/* reach EOF */
        {
          errno = ZERO_BYTE_TRANSFER_ERRNO;
          break;
        }
      total += n_rw;
      ptr += n_rw;
      count -= n_rw;
    }

  return total;
}

Tips : 查看lib文件夹下safe_read.c 和 safe_write.c文件能够看到这个函数如何被展开成两个不同的函数的.


6. cat 函数, 处理格式化输出

simple_cat仅仅是将输入原封不动的输出, 没有作不论什么处理, 全部与格式化输出有关的内容都放在了 cat 函数里面.

cat 函数的实现包括非常多技巧. 比如使用一个哨兵'\n'来标记输入缓冲区的结尾. 另外使用了一个字符数组来统计行数, 使得不支持64位整型的系统也能够使用非常大范围的数.

以下是这个行计数器的代码.

/* Position in 'line_buf' where printing starts.  This will not change
   unless the number of lines is larger than 999999.  */
static char *line_num_print = line_buf + LINE_COUNTER_BUF_LEN - 8;

/* Position of the first digit in 'line_buf'.  */
static char *line_num_start = line_buf + LINE_COUNTER_BUF_LEN - 3;

/* Position of the last digit in 'line_buf'.  */
static char *line_num_end = line_buf + LINE_COUNTER_BUF_LEN - 3;

static void
next_line_num (void)
{
  char *endp = line_num_end;
  do
    {
      if ((*endp)++ < '9')
        return;
      *endp-- = '0';
    }
  while (endp >= line_num_start);
  if (line_num_start > line_buf)
    *--line_num_start = '1';
  else
    *line_buf = '>';
  if (line_num_start < line_num_print)
    line_num_print--;
}

理解这个函数的关键是搞懂newlines这个变量作用, cat格式化输出基本的操作推断换行和连续空行, newlines这个变量标记的是空行的数目, 值为0表示此时的inbuf的读取位置在一行的开头, 1表示有一个空行, -1 表示刚刚解析完一行, 准备进入下一行, 能够看到cat 函数最后的那个 while(true) 的两个break语句都将 newlines 置为 -1.

cat格式化输出的过程实际上就是逐一扫描输入缓冲数组的过程, 并在扫描的过程中将转化后的字符存入输出缓冲数组中.


转载于:https://www.cnblogs.com/ljbguanli/p/6734168.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值