pipe_open源码分析

在使用open函数打开管道时,会对管道的inode节点进行内存分配:

    /* 防止并发,上锁保护 */ 
    spin_lock(&inode->i_lock);
    /* 首次打开管道,需要申请内存,这里判断是否已经分配内存 */
    if (inode->i_pipe) {
        pipe = inode->i_pipe;
        /* 每打开一次管道files就会++ */ 
        pipe->files++;
        spin_unlock(&inode->i_lock);
    } else {
        spin_unlock(&inode->i_lock);
        /* 给管道分配内存 */ 
        pipe = alloc_pipe_info();
        if (!pipe)
            return -ENOMEM;
        pipe->files = 1;
        spin_lock(&inode->i_lock);
        if (unlikely(inode->i_pipe)) {
            inode->i_pipe->files++;
            spin_unlock(&inode->i_lock);
            free_pipe_info(pipe);
            pipe = inode->i_pipe;
        } else {
            inode->i_pipe = pipe;
            spin_unlock(&inode->i_lock);
        }
    }

1.1 只读方式打开

打开fifo后,如果当前没有writer,那么会进入休眠状态,等待writer写入数据后,唤醒等待队列中的reader(以只读方式打开):

    case FMODE_READ:
    /*
     *  O_RDONLY
     *  POSIX.1 says that O_NONBLOCK means return with the FIFO
     *  opened, even when there is no process writing the FIFO.
     */
        pipe->r_counter++;
        /* 如果当前管道上的reader为0,唤醒writer端,让对端先写数据。 */ 
        if (pipe->readers++ == 0)
            wake_up_partner(pipe);
        /* 如果writer为0,那么需要等待writer写入信息 */
        if (!is_pipe && !pipe->writers) {
            if ((filp->f_flags & O_NONBLOCK)) {
                /* suppress EPOLLHUP until we have
                 * seen a writer */
                filp->f_version = pipe->w_counter;
            } else {
                /* 如果没有writer会休眠等待 */ 
                if (wait_for_partner(pipe, &pipe->w_counter))
                    goto err_rd;
            }
        }
        break;

因此阻塞可以分为两种:

  1. 当打开fifo并且发现没有writer的时候,此时是没有数据的,进程会进入休眠模式等待被唤醒

  2. 如果有writer但是没有数据,也会进入休眠模式,等待writer写入数据后被唤醒

static void wake_up_partner(struct pipe_inode_info *pipe)
{
    /* 如果pipe的wait等待队列为空,直接返回 */
	wake_up_interruptible(&pipe->wait);
}

wait_for_partner函数实际上这里就是等待writer,在创建fifo的写端时,是会给pipe->w_counter进行自增操作,所以可以根据w_couter的值来判断是否有写端被创建:

static int wait_for_partner(struct pipe_inode_info *pipe, unsigned int *cnt)
{
	int cur = *cnt;	
	/* 这里有个trick,传入的cnt是个指针(指向pipe->w_counter或者pipe->r_counter),
	   pipe->w_counter(pipe->r_counter)在打开管道时会根据
	   读写模式来修改这个值。如果没有新增的对端就会一直等待。 */
	while (cur == *cnt) {
		pipe_wait(pipe);
		if (signal_pending(current))
			break;
	}
	return cur == *cnt ? -ERESTARTSYS : 0;
}

1.2 只写方式打开

    case FMODE_WRITE:
    /*
     *  O_WRONLY
     *  POSIX.1 says that O_NONBLOCK means return -1 with
     *  errno=ENXIO when there is no process reading the FIFO.
     */
        ret = -ENXIO;
        /* 在没有reader,以只写且非阻塞打开fifo的情况下,直接报错 */ 
        if (!is_pipe && (filp->f_flags & O_NONBLOCK) && !pipe->readers)
            goto err;

        pipe->w_counter++;
        /* 唤醒之前没有写端而导致阻塞的读进程 */ 
        if (!pipe->writers++)
            wake_up_partner(pipe);

        /* 没有reader则一直休眠等待 */ 
        if (!is_pipe && !pipe->readers) {
            if (wait_for_partner(pipe, &pipe->r_counter))
                goto err_wr;
        }
        break;

如果以只写方式打开,那么如果没有reader,这种情况下直接报错。

1.3 读写方式打开

    case FMODE_READ | FMODE_WRITE:
    /*
     *  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.
     */

        pipe->readers++;
        pipe->writers++;
        pipe->r_counter++;
        pipe->w_counter++;
        /* 当以读写方式打开管道,reader和writer的值都为1
           需要另外程序打开对端管道 */
        if (pipe->readers == 1 || pipe->writers == 1)
            /* 唤醒阻塞的对端 */
            wake_up_partner(pipe);
        break;
posted @ 2025-04-23 10:34  cockpunctual  阅读(25)  评论(0)    收藏  举报