博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

接着上次的C语言问题(转)

Posted on 2013-05-19 09:12  凤凰火舞  阅读(181)  评论(0编辑  收藏  举报

上篇博文讲到一个诡异的C语言问题 ,这次转自别人的一个解释,希望能记录下来,对大家深入理解其中机制有一定的帮助。

上回的问题

 1 #include <stdio.h>
 2 
 3 int main()
 4 {
 5     char buff[50];
 6 
 7     while (true)
 8     {
 9         scanf("%s", buff);
10         printf("you inputed: %s\n", buff);
11     }
12     return 0;
13 }

我们需要将重定向后的scanf 在读取完文件以后仍然能够阻塞读取用户的输入,我利用Linux上面的strace 看了一遍scanf() 函数的系统调用

说说重定向吧,先把我的思路说一遍

首先这个问题当初我以为是重定向的时候在数据文件和进程中间加入了一个管道,而我试图去找到管道的描述符,然后利用情况缓冲区将管道内的数据情况,这就是我失败的地方,因为根本不存在那么一个描述符。

于是昨天看完文章以后决定看看scanf()的时候都做了些什么

我的程序命名为 pscanf

利用strace 查看系统调用

in.txt 内容是这样的

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccc
dddddddddddddddddddddddddddddd
eeeeeeeeeeeeeeeeeeeeeeeeee
ffffffffffffffffffffffffffffffffffffffffffff
> strace ./pscanf < in.txt

可以看到,scanf()  函数实际上是调用了read() 函数

而且,最重要的是读取地址位于 0 ,也就是标准输入设备

而我们已经重定向了输入流,为什么还是在标准输入设备读取呢?所以,如果我们试图找到描述符然后清空缓冲区是不行的,因为根本不存在改变描述符

标准输入意味着什么?

在Linux中,它抽象为一个文件 在 /dev/ 目录下的一个文件而已,底层是经过文件系统抽象的,这里可以暂时先将它看做是一个文件 在 /dev/stdin 这里

然后我们看看 read() 函数在执行过程中经过那些步骤

 1 asmlinkage ssize_t sys_read(unsigned int fd, char __user * buf, size_t count)
 2 {
 3    struct file *file;
 4    ssize_t ret = -EBADF;
 5    int fput_needed;
 6 
 7    file = fget_light(fd, &fput_needed);
 8    if (file) {
 9      loff_t pos = file_pos_read(file);
10      ret = vfs_read(file, buf, count, &pos);
11     file_pos_write(file, pos);
12      fput_light(file, fput_needed);
13    }
14 
15    return ret;
16 }

可以看到,read() 函数调用了系统调用函数sys_read()

第一个参数是ssize_t read(int fd,void * buf ,size_t count);

这个函数传递来的文件描述符,而这里就是0 也就是标准输入设备的描述符

第一个函数fget_light( fd , &fput_needed)  作用是取得文件描述符对应的文件指针

而 file_pos_read( file ) 是根据文件指针找到当前文件的位置

如果传入的是0 ,我们可以知道 这个位置就应该是 /dev/stdin  而如果我们重定向以后,这个位置就是当前的文件重定向后的位置,所以到这里,就可以知道重定向其实就是改变了操作目录。在为终端模式下这个路径应该是/dev/tty0 1 2 3 4…

那么 ,从上面来看,read() 其实读取的时候是读完了整个文件,返回大小是读取到的整个文件的byte 数,却不是一行一行的读取呢?

read() 俗称不带缓冲区的I/O 函数,意思是这个系统调用不需要用户为其分配内核的缓冲区,那么read()一般读取的时候会将1024 2048 4096 这样的大小作为自己缓冲区的大小,这是为什么呢?

因为系统读取数据的时候会有一个预读,也就是说读取到CPU 的缓冲区里面,一般设置为行读取,这样刚好就是1024的整数倍,这意味着每次都会预读一部分到CPU 缓冲区,所以我们可以看到在查看系统调用的时候会将整个文件的内容读取到内核缓冲区,然后输出的时候依据行缓存的printf打印出输出的值(注意read() 函数里面的 \n) 。

当read这个系统调用继续读取in.txt 的时候由于没有数据,仍然会继续读取。

这个就是重定向的一部分我自己的猜测,可能有错误的地方,希望大侠指正。转载