fd与重定向

我们可以通过这样的方式来重定向,关闭fd 1,然后打开一个新文件。
那么原来写向stdout的东西,就会全部写到这个文件中。
即使是这样的在shell中重定向,也是无效的
cmd > result.txt
输出还是会写到我们打开的那个新文件中。

int main(int argc,char* argv[]){
if(close(1) !=0 ){
ACE_ERROR_RETURN((LM_ERROR,ACE_TEXT("%p"),ACE_TEXT("close fd 1")),-1);
}
int fd=open("test.txt",O_RDWR|O_CREAT|O_TRUNC,S_IWUSR|S_IRUSR);
if(fd==-1){
ACE_ERROR_RETURN((LM_ERROR,ACE_TEXT("%p"),ACE_TEXT("open new 
file")),-1);
}
std::cout<<"The fd of new file is:"<<fd<<std::endl;
return 0;
}

关闭fd 1后,不写
-r-x--s--- 1 snnn wheel 0B 5 2 12:08 test.txt
关闭fd 1后,写入
--wxr-x--- 1 snnn wheel 24B 5 2 12:03 test.txt
令人好奇的是这个权限位
后来我发现这个和是否关闭fd 1没有关系,问题在于我open new file的时
候添加了O_CREAT参数但是没有指定mode,于是,它可能越界读取了错误的
值作为mode.
关于open的mode的一些补充
传递给open的mode并不是实际最终的mode.它需要先和umask进行mask.
最终的mode为 ~umask & mode
例如,假如我的umask是022(默认),而传递给open的mode是
S_IWUSR|S_IWGRP,那么实际上S_IWGRP这个bit根本不会起作用。
关于fd的一些补充
进程刚开始的时候,首先要把locale设置为"C",然后打开3个fd,"0,1,2",
分别是stdin,stdout,stderr.如果在程序的一开始调用一个open,那么返回
的fd一定是3.如果是先把已打开的3个标准io流关闭一个,例如关闭1,那么
重新打开的fd就会是1。于是这个fd和STDOUT_FILENO就完全是一个东西。
(它们的值一样),于是写往stdout的所有东西就都会写往这个文件。
但是这种重定向的方式原理上于dup是不同的。
dup是共享文件指针等。而这里不是。
关于open的实现
1。将flags与O_ACCMODE进行&,以检查O_RDONLY,O_WRONLY,O_RDWR中的至少一个已
被设置。
if ((flags & O_ACCMODE) == O_ACCMODE)
return (EINVAL);
这个flags很有趣,存在一种"经典模式"和"现代模式".^_^
for some historic reasons,
O_RDONLY,O_WRONLY,O_RDWR这三个标志位是被定义为0,1,2,

#define O_RDONLY 0x0000 /* open for reading only */
#define O_WRONLY 0x0001 /* open for writing only */
#define O_RDWR 0x0002 /* open for reading and writing */
#define O_ACCMODE 0x0003 /* mask for above modes */

必须,且只能定义一个。这种古怪的定义让人非常之舒服。于是FreeBSD在
内部实现的时候先把这种老的FLAGS称之为OFLAGS('O' standard for
"open")。而把自己定义的新格式的FLAGS称之为FFLAGS('F' standard for
"FreeBSD").主要区别就是R和W分别各占一位而不是挤在一起。FFLAGS从1
开始。

#if __BSD_VISIBLE
#define FREAD 0x0001
#define FWRITE 0x0002
#endif

然后在Kernel中定义一个简单的转换。

/* convert from open() flags to/from fflags; convert O_RD/WR to 
FREAD/FWRITE */
#define FFLAGS(oflags) ((oflags) + 1)
#define OFLAGS(fflags) ((fflags) - 1)

在检查完ACCMODE后,把oflags转换为fflags

flags = FFLAGS(flags);

然后调用falloc为文件分配空间和文件描述符。

error = falloc(td, &nfp, &indx);

其中td是指向当前线程的struct thread.
falloc的主要作用是,用uma_zalloc在磁盘上为这个文件分配空间,然后为该进程分配指向这个file的文件描述符。
这个文件一旦被创建后。一般将会把reference count(file->f_count)设置为2.一个是为自己进程的file descriptor table.一个是为resultfp.

falloc(struct thread *td, struct file **resultfp, int *resultfd)

resultfp是一个作为返回值的指针。文档中说这是因为
“This is to prevent us being preempted and the entry in the descriptor
table closed after we release the FILEDESC lock.”
当然,如果传入的resultfp==NULL,那么就不用了。

fp->f_count = 1;
if (resultfp)
fp->f_count++;

其实我觉得这2句代码应该写做

if (resultfp)
fp->f_count=1;
else fp->f_count=2;

继续初始化fp,

fp->f_cred = crhold(td->td_ucred);
fp->f_ops = &badfileops;
fp->f_data = NULL;
fp->f_vnode = NULL;

然后接下来

/*
* If the process has file descriptor zero open, add the new file
* descriptor to the list of open files at that point, otherwise
* put it at the front of the list of open files.
*/

if ((fq = p->p_fd->fd_ofiles[0])) {
    LIST_INSERT_AFTER(fq, fp, f_list);
} else {
    LIST_INSERT_HEAD(&filehead, fp, f_list);
}

这里就产生了一个疑问,fd究竟是担当着一个什么样的角色?
每一个进程,proc,有一个struct filedesc* proc::p_fd,就是我们常说的文件描
述符表。
这个表中第一项,就是file ** fd_ofiles,就是该进程当前所有打开的文件。它是
一个数组,而不是一个list.而fd,就是这个这个数组的索引。于是
fd_ofiles[0],就指向stdin,fd_ofiles[1]就指向stdout,fd_ofiles[2]就指向stderr,
这就解释了为什么当我们打开一个新文件的时候,open返回的fd一定是当前进程可
用的最小的文件描述符。因为它是一个支持随机访问的数组,而不是 proc那样的list.
falloc的末了,分配一个fd,

fdalloc(td, 0, &i); //第二个参数0,代表分配fd时要求fd的最小为0

然后加到当前进程的fd_ofiles里面去。

p->p_fd->fd_ofiles[i] = fp;

最后将fp和i返回。
(如果C支持传引用就好了,声明函数的时候就不用多加一层指针了)
再来仔细看看系统是如何分配fd的。

/*
* Allocate a file descriptor for the process.
*/
int fdalloc(struct thread *td, int minfd, int *result){
    struct proc *p = td->td_proc;
    struct filedesc *fdp = p->p_fd;
    int fd = -1, maxfd;

    FILEDESC_LOCK_ASSERT(fdp, MA_OWNED);

    //获取minfd
    //fdp->fd_freefile,推荐的,下一个可用fd.
    if (fdp->fd_freefile > minfd)
    minfd = fdp->fd_freefile; 

    //获取maxfd
    PROC_LOCK(p);
    maxfd = min((int)lim_cur(p, RLIMIT_NOFILE), maxfilesperproc);
    PROC_UNLOCK(p);

    /*
    * Search the bitmap for a free descriptor. If none is found, try
    * to grow the file table. Keep at it until we either get a file
    * descriptor or run into process or system limits; fdgrowtable()
    * may drop the filedesc lock, so we're in a race.
    */
    for (;;) {
        fd = fd_first_free(fdp, minfd, fdp->fd_nfiles);
        //满了
        if (fd >= maxfd)
            return (EMFILE);
        if (fd < fdp->fd_nfiles)
            break;
        //如果minfd<fdp->fd_nfiles,则fd_first_free会返回minfd
        //把fdp增长为原来的2倍大,最大不超过maxfd
        fdgrowtable(fdp, min(fdp->fd_nfiles * 2, maxfd));
        //然后继续搜索
    }

    /*
    * Perform some sanity checks, then mark the file descriptor as
    * used and return it to the caller.
    */
    KASSERT(!fdisused(fdp, fd),
    ("fd_first_free() returned non-free descriptor"));
    KASSERT(fdp->fd_ofiles[fd] == NULL,
    ("free descriptor isn't"));

    ///为了安全期间,先把这个它的flags设置为0
    fdp->fd_ofileflags[fd] = 0; /* XXX needed? */
    /*
    * Mark a file descriptor as used.
    */
    ///更新fdp->fd_lastfile和fdp->fd_freefile
    fdused(fdp, fd);
    *result = fd;
    return (0);
}

fd_first_free的搜索算法比较复杂,是从filedesc的fd_map中搜索。这个fd_map
究竟是做什么的我还不是很明白。以后再看。
然后回来看,当我们获得新分配的fp后,要做什么?

/* An extra reference on `nfp' has been held for us by falloc(). */
fp = nfp;

这才是完整的计算mode的算法

cmode = ((mode &~ fdp->fd_cmask) & ALLPERMS) &~ S_ISTXT;

"& ALLPERMS"是为了防止有多余的位出现。
"&~ S_ISTXT"则是为了忽略S_ISTXT位
最后说一句,open返回的fd,通常是,但不一定是,可用的最小描述符。

此博客中的热门博文

少写代码,多读别人写的代码

在windows下使用llvm+clang

tensorflow distributed runtime初窥