• bash中管道命令返回值如何确定(下)


    一、shell中'>'何时清空文件
    由于bash虽然功能没有C语言那么强大,但是它同样是有自己特殊语法,它的整个语法文件的解析也是通过一个yacc文件来定义,其中对于我们关心的'>'重定向实现来说,其语法文件的定义为:
    bash-4.1parse.y
    redirection:    '>' WORD
                {
                  source.dest = 1;
                  redir.filename = $2;
                  $$ = make_redirection (source, r_output_direction, redir, 0);
                }
    然后在接下来的相关处理函数中,其中对于该选项的处理为:
    make_redirection (source, instruction, dest_and_filename, flags)
      switch (instruction)
        {

        case r_output_direction:        /* >foo */
        case r_output_force:        /* >| foo */
        case r_err_and_out:            /* &>filename */
          temp->flags = O_TRUNC | O_WRONLY | O_CREAT;
          break;
    其中的O_TRUNC选项相当霸道,如果有这个选项,那么当文件打开的时候,这个文件就会被清零。由于可执行文件的文件描述符都是由shell在创建子进程之前由shell打开文件,然后供子进程继承的,所以意味着在子进程还没有开始执行的时候,它使用的文件已经被清空。
    内核中对于该选项的处理应该是位于may_open--->>do_truncate(dentry, 0, ATTR_MTIME|ATTR_CTIME, NULL),直接将文件大小清空为零,不过这也以为这文件是比较容易恢复的,如果说之后硬盘没有写入操作的话。
    这一点其实是和C库中的fopen函数中的w选项类似,如果说你在fopen一个文件的时候添加了w选项,这个文件同样会在这个函数成功返回之后被清空,glibc-2.7libiofileops.c
        case 'w':
          omode = O_WRONLY;
          oflags = O_CREAT|O_TRUNC;
          read_write = _IO_NO_READS;
          break;
    那么如何以只写方式打开并且文件,那么可以使用‘r’选项,但是缺点就是如果希望从文件头开始写入,那么需要自己lseek,不过这样一般是没有什么道理的,所以事实上可能不需要有这种应用。
    二、管道写入方提前退出
    这个读入操作的判断对于写入者的依赖事实上并没有那么严格,至少说没有读入方提前退出那么严重,这个文件之后再讨论。现在假设说管道中的两个哥俩正在手拉手开开心心的执行命令,然后某一个无征兆退出,那么另一方会怎么应对这个退出事件。为了说明实际问题,我们就不用那个最为经典的
    sleep 1234 | sleep 5678
    这种模型了,而是选择最会讲话的yes命令和最善于聆听的tee命令来组成管道:
    yes my lord | tee > /dev/null
    现在如果将管道的写入端yes命令杀死,那么此时tee会有什么行为。测试一下可以发现,这个tee对这个状况是坦然面对,淡定退出运行,返回值为零,并且这个返回是实时的,也就是在yes命令退出之后tee也同时退出。
    当管道写入端退出时,会执行对应的close操作:
    pipe_write_release-->>pipe_release
    if (!pipe->readers && !pipe->writers) {
            free_pipe_info(inode);
        } else {只要读者为零或者写者为零,则会执行管道上阻塞等待者的唤醒
            wake_up_interruptible(&pipe->wait);
            kill_fasync(&pipe->fasync_readers, SIGIO, POLL_IN);
            kill_fasync(&pipe->fasync_writers, SIGIO, POLL_OUT);
        }
    当读入端被唤醒的时候,它执行的判断为:
    linux-2.6.21fspipe.c
    static ssize_t
    pipe_read(struct kiocb *iocb, const struct iovec *_iov,
           unsigned long nr_segs, loff_t pos)

            if (bufs)    /* More to do? */
                continue;
            if (!pipe->writers)
                break;
    如果写入者为零,也就是没有写入者的时候,此处将会直接返回而不会再尝试同步等待数据写入,然后在用户态也就是体现为read系统调用返回值为零,而tee程序(大部分比较严谨的程序)在read返回值为零的时候一般都会推出任务,所以管道组退出。
    三、管道读入方提前退出
    相对于写入方提前退出来说,这个是一个比较严重的问题,因为当一个管道写入时,如果没有人在读取,那么此时内核会毫不客气的发送一个SIGPIPE信号,而这个信号的默认行为是置人于死地的。这个管道是和socket的行为一致的,而当初pipe是sysv系统的进程通讯方式,而socket则是berkeley的通讯方式,所以虽然是两种不同的机制,但是其中的哲理还是比较相似的。当然如果程序对此有遇见,并且比较不按套路出牌就可以主动忽略这个信号,这样别人不断的发,程序不断的忽略就好了。
    那么为什么会这样呢?我想可能是read毕竟是一个对系统无害的动作,如果读不到内容,那么也无所谓,如实返回零就好了。但是对于write来说,它是需要消耗系统资源的,而这个消耗注定是浪费的,所以必须及时制止。或者说一堆人坐在一起,你在那个地方一直洗耳恭听,但是没有人讲话,大家觉得这人也没啥讨厌的,就是内向一点;但是如果有人不管其他人愿不愿意听,就一直讲,那大家可能就不乐意了。
    linux-2.6.21fspipe.c
    pipe_write(struct kiocb *iocb, const struct iovec *_iov,
            unsigned long nr_segs, loff_t ppos)
        if (!pipe->readers) {如果管道的读入端已经没有人来读取,那么写入操作将会受到SIGPIPE信号,并且read返回值为负数
            send_sig(SIGPIPE, current, 0);
            ret = -EPIPE;
            goto out;
        }
    四、命名管道
    命名管道是为了解决匿名管道的生存期依赖两端进程生命期的限制而提出的,它可以超越进程而存在,从而可以在更大的空间和时间范围内共享。
    linux-2.6.21fsfifo.c
    这个管道其实是一个特殊节点文件,也就是通过mknod创建节点文件。它和匿名管道的最大区别在于它的open接口和管道的不同,事实上,匿名管道是通过pipe系统调用创建的,所以用户不用关心文件的打开,这个打开由内核带来。但是对于命名管道来说,它可以有多个读入者和写入者,并且作为一个文件,可以以不同的读写方式来打开,所以它比较精髓的地方就是对管道的打开的时候进行了同步操作。大家要注意一下这个同步时机,是在文件打开的时候同步的,这意味着在文件真正的读写时,它的行为和匿名管道是相同的,另一方面,和通常的read/write阻塞不通,这个文件打开也是很有可能阻塞的
    static int fifo_open(struct inode *inode, struct file *filp)

        switch (filp->f_mode) {
        case 1:
        /*
         *  O_RDONLY
         *  POSIX.1 says that O_NONBLOCK means return with the FIFO
         *  opened, even when there is no process writing the FIFO.
         */
            filp->f_op = &read_fifo_fops;
            pipe->r_counter++;
            if (pipe->readers++ == 0)第一个读者打开文件之后,可以唤醒可能被阻塞的写者进程
                wake_up_partner(inode);

            if (!pipe->writers) {如果管道上海没有写入进程
                if ((filp->f_flags & O_NONBLOCK)) {
                    /* suppress POLLHUP until we have
                     * seen a writer */
                    filp->f_version = pipe->w_counter;
                } else 
                {
                    wait_for_partner(inode, &pipe->w_counter);等待写入者介入,此时open操作可能阻塞
                    if(signal_pending(current))
                        goto err_rd;
                }
            }
            break;
        
        case 2:写入几乎是读出的镜像操作,所以不再详细注释
        /*
         *  O_WRONLY
         *  POSIX.1 says that O_NONBLOCK means return -1 with
         *  errno=ENXIO when there is no process reading the FIFO.
         */
            ret = -ENXIO;
            if ((filp->f_flags & O_NONBLOCK) && !pipe->readers)
                goto err;

            filp->f_op = &write_fifo_fops;
            pipe->w_counter++;
            if (!pipe->writers++)
                wake_up_partner(inode);

            if (!pipe->readers) {
                wait_for_partner(inode, &pipe->r_counter);
                if (signal_pending(current))
                    goto err_wr;
            }
            break;
        
        case 3:可读可写打开,这个自给自足,自产自销,比较强大
        /*
         *  O_RDWR
         *  POSIX.1 leaves this case "undefined" when O_NONBLOCK is set.
         *  This implementation will NEVER block on a O_RDWR open, since
         *  the process can at least talk to itself.
         */
            filp->f_op = &rdwr_fifo_fops;

            pipe->readers++;
            pipe->writers++;
            pipe->r_counter++;
            pipe->w_counter++;
            if (pipe->readers == 1 || pipe->writers == 1)
                wake_up_partner(inode);
            break;

        default:
            ret = -EINVAL;
            goto err;
        }
     
     
     
     
     
  • 相关阅读:
    Spark源码学习1.1——DAGScheduler.scala
    Spark随笔(三):straggler的产生原因
    Spark随笔(二):深入学习
    Spark随笔(一):Spark的综合认识
    Hadoop随笔(二):Hadoop V1到Hadoop V2的主要变化
    Hadoop随笔(一):工作流程的源码
    zookeeper 安装笔记 3.6.7
    OpenStack 与 大数据的融合
    三 概要模式 3) MR计数器计数 。无 reduce 计数
    五 数据组织模式 2) 分区模式 代码
  • 原文地址:https://www.cnblogs.com/tsecer/p/10486331.html
Copyright © 2020-2023  润新知