1、作用
shell中经常会使用到IO重定向,
bash test.sh >/dev/null 2>&1
0、1、2为文件描述符在默认情况下,分别表示进程的标准输入、标准输出、标准错误输出。I/O重定向可以将这些标准的输入输出重定向到其他文件上,例如上面语句作用是,将标准输出和标准错误输出重定向到文件/dev/null这个黑洞文件里面,即不输出
2、常用场景
shell常用的方法如下
exec n>filename //开启新的描述符n并把目标指向对应的文件filename
echo >&n "some thing"
echo >>&n "some thing"
exec n>&- //关闭描述符n
3、原理
说明io重定向先简单说明内核如何表示一个打开的文件,在一个进程的内核结构中维护着两张表表示该进程已打开的文件(这里简单说明,并不严谨),可以如下所示
简化版内核文件表示
用户层打开一个文件内核将分配一个fd和file结构并填入进程的fd和file表中,fd和file保持对应的关系,io重定向即可以修改这种对应关系,内核提供dup和dup2等系统调用完成这项功能
已dup2为例
2>&1对应调用dup2(1,2)
调用后fd和file的对应关系将变为如下形式,其中file2标识为灰色表面内核可能关闭file2结构
dup(1,2)
了解上述原理后简单说明以下两者的区别,通过实例图应该比较清楚
>/dev/null 2>&1
dup(/dev/null,1)
dup(1,2)
2>&1 >/dev/null
dup(1,2)
dup(/dev/null,1)
>/dev/null 2>&1与2>&1 >/dev/null
4、内核中dup实现
对于上述原理理解后对应IO重定向应该有比较形象的理解了,但是还是存在一些盲点,即被重定向的文件描述符是一个未打开过的文件,还是一个打开的文件,如果已打开,被重定向后,源文件是否会被关闭,带着这些疑问,看看内核源码是怎么实现的吧,下述源码对应内核版本为2.6.18
SYSCALL_DEFINE3(dup3, unsigned int, oldfd, unsigned int, newfd, int, flags)
{
int err = -EBADF;
struct file * file, *tofree;
struct files_struct * files = current->files;
struct fdtable *fdt;
spin_lock(&files->file_lock);
//获取oldfd处的file结构
file = fcheck(oldfd);
if (unlikely(!file))
goto Ebadf;
//扩展当前进程的files结构,返回值<0,0,1
err = expand_files(files, newfd);
if (unlikely(err < 0)) {
if (err == -EMFILE)
goto Ebadf;
goto out_unlock;
}
err = -EBUSY;
//获取当前进程的fd table
fdt = files_fdtable(files);
//保存在newfd这个位置上,之前的file结构
tofree = fdt->fd[newfd];
//异常情况检查
if (!tofree && FD_ISSET(newfd, fdt->open_fds))
goto out_unlock;
//增加oldfd的file的引用计数
get_file(file);
//将newfd处的file指针指向 oldfd的file结构
rcu_assign_pointer(fdt->fd[newfd], file);
//将newfd加入openfd列表中
FD_SET(newfd, fdt->open_fds);
if (flags & O_CLOEXEC)
FD_SET(newfd, fdt->close_on_exec);
else
FD_CLR(newfd, fdt->close_on_exec);
spin_unlock(&files->file_lock);
//如果原先newfd处已经打开过文件,则关闭对应的文件
if (tofree)
filp_close(tofree, files);
return newfd;
Ebadf:
err = -EBADF;
out_unlock:
spin_unlock(&files->file_lock);
return err;
}
SYSCALL_DEFINE2(dup2, unsigned int, oldfd, unsigned int, newfd)
{
if (unlikely(newfd == oldfd)) { /* corner case */
//检查oldfd是否在进程已打开的文件列表中
struct files_struct *files = current->files;
int retval = oldfd;
rcu_read_lock();
if (!fcheck_files(files, oldfd))
retval = -EBADF;
rcu_read_unlock();
return retval;
}
return sys_dup3(oldfd, newfd, 0);
}
通过源码可以比较清楚的发现
dup(m,n)中
1、n可以是一个全新的文件描述符即还未分配给已打开的文件,此时内核将分配该fd,并把该对应关系执行m执行的file结构,并对该file结构引用+1
2、n也可以是一个已经被分配给打开文件的描述符,此时内核将重新将该fd的指向改为m指向的file结构,引用+1,之后对原先n指向的file结构尝试关闭
网友评论