UNIX 重定向>、2>&1 及内部实现

1,068 阅读3分钟

在crontab配置定时任务,经常会把程序输出重定向到文件或/dev/null,比如*/15 * * * * php /data0/log2db/logdbplan.php >/tmp/logdbplan.log 2>&1把程序日志和报错写到logdbplan.log文件,>/tmp/logdbplan.log1>/tmp/logdbplan.log 简写形式,一直疑惑重定向怎么实现的?

0、1、2文件描述符含义

系统默认给程序打开三个文件描述符,分别为0、1、2,当然程序启动后可以选择关闭某些文件描述符,可以使用 lsof -a -p 进程pid -d0,1,2 查看具体信息

0和stdin  :标准输入
1和stdout:标准输出
2和stderr :标准错误

>、>> 重定向

>是清空文件,然后写入。>>相当于追加模式。我们可以使用代码模拟一下

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[]){   
    close(1); // 关闭标准输出
    close(2); //关闭标准错误

    // UXIN 从0开始寻找可以使用的文件描述符,第一个open将返回1这个文件描述符
    open("./out.txt", O_CREAT | O_WRONLY | O_TRUNC ); 
    open("./err.txt", O_CREAT | O_WRONLY | O_TRUNC );

    printf("标准输出---1\n");
    fprintf(stdout, "标准输出---2\n");
    write(1, "标准输出---3\n", strlen("标准输出---3\n"));

    fprintf(stderr, "标准错误---1\n");
    write(2, "标准错误---2\n", strlen("标准错误---2\n"));
    
    return 0;
}

在代码中我们先关闭标准输出和标准错误描述符,然后打开stdout和stderr描述符。程序编译执行结果如下: 可以看到标准输出的三条日志写入了out.txt文件中,两条错误信息写入了err.txt文件,类似于 cmd 1>out.txt 2>err.txt执行程序,然而2>&1又是如何实现的呢?我们修改下程序,把标准输出赋值给标准错误描述符试一下,代码如下:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>

int main(int argc, char *argv[]){
   
    close(1); // 关闭标准输出
    close(2); //关闭标准错误

    // UXIN 从0开始寻找可以使用的文件描述符,第一个open将返回1这个文件描述符
    int fd = open("./out.txt", O_CREAT | O_WRONLY | O_TRUNC ); 
    // open("./err.txt", O_CREAT | O_WRONLY | O_TRUNC );

    printf("标准输出---1\n");
    fprintf(stdout, "标准输出---2\n");
    write(1, "标准输出---3\n", strlen("标准输出---3\n"));

    // 把 标准输出 描述符,赋值给 标准错误 描述符
    stderr = stdout;
    fprintf(stderr, "标准错误---1\n");
    write(fd, "标准错误---2\n", strlen("标准错误---2\n"));
    return 0;
}

和上面代码基本一样,我们只是给stderr赋值,然后编译执行,执行结果如下: 可以看出2条标准出错和3条标准输出都成功写入了out.txt文件,>/tmp/logdbplan.log 2>&1的重定向和这个程序类似,是把logdbplain.log的文件句柄给了标准输出,而2>&1&表示取地址,标准出错引用标准输出的文件描述符,所以标准输出和标准出错信息,都写入了out.txt。

番外篇:文件IO

现在简单解释下为何两张截图中的结果并不是代码写入的顺序,一切都是系统优化的锅。printffprintf是把数据拷贝到标准库(这里的C库)的缓存区然后返回,操作的是用户进程空间的缓存。待某个时刻调用write拷贝到内核缓存,最终写到磁盘上。printffprintf合并用户多次写,减少写盘次数,提高性能。

write是异步把数据拷贝到内核的page cache,会有内核态与用户态的切换。内核拿到数据后并不会直接写到磁盘上,会有IO调度系统决定,当然也可以设置write同步写到磁盘,在open时设置O_SYNC标志,会强制刷新内核缓冲区到磁盘文件。

IO调度系统也是合并多次写,并把写事件排序,尽量按照磁盘选择方向和磁头前进方向排序,把多次随机写,优化成顺序写,因为磁头旋转寻址很慢,而读写相对很快。当然如果是SSD硬盘,没必要排序,只需要合并写事件减少写盘次数