关于文件描述符--不能不知的几件事

文件描述符和指针的区别,请看博文http://blog.chinaunix.net/uid-20672257-id-1901040.html
close(1) 和 fclose(stdout) 的区别,请看博文http://blog.csdn.net/wangzuxi/article/details/43445599

在linux中,everything is file. 每打开一个文件,就能获得一个文件描述符,它是一个很小的整数,Linux中每个进程的进程控制块PCB中,都有一个文件描述符表,而文件描述符就是这个表索引,表项指向一个已打开的文件的指针。

使用系统调用open,可以返回一个文件描述符,使用libc 库函数fopen,能够得到FILE* 的文件指针。文件描述符,就是进程文件描述符表的一个索引,而文件指针保存的是FILE结构的地址,能够用于表示开放的IO流。
FILE结构包括读写缓冲区,flag以及文件标识符还有文件计数器等。

文件描述符的有效范围是0 到 OPEN_MAX,经测试,我的环境下是从3-1023,因为0 1 2分别对应的是stdin, stdout, stderr。每一个进程能够打开的文件数目是有限制的,测试我的环境为99个。

了解了进程描述符和FILE* 文件指针,那么现在来测试一下。
文件描述符1对应的是stdout标准输出,如果我将1 关闭,然后重新打开一个文件,该文件的文件描述符应该就是1,如果这个时候我使用printf函数,会不会直接将结果输出到新打开的文件中呢?

close(1);
int fd = open("1.txt", O_RDWR);
if(fd == -1)
     perror("open 1.txt");
printf("printf hello world\n");
char buf[24] = "write fd hello world";
write(fd, buf, strlen(buf));
close(fd);

程序运行结果为:
结果
结果显示,在1.txt文件中,并没有printf的内容,只有write 进去的内容,这是因为close(1)关闭了标准输出,printf是输出到标准输出上的,使用strace 查看:
关于文件描述符--不能不知的几件事_第1张图片
图中可以看到,调用printf时,其实调用了puts,最后还是通过系统调用write来写的,但是返回的是-1,表示写失败了,所以屏幕上不会显示printf的内容,同时,也表示通过文件描述符1 在表中的索引不再指向的标准输出的文件指针,此时,重新打开一个文件后,文件描述符1 找到的是该文件的文件指针。

针对文件指针指向的file 结构,它有一个很重要的成员f_count,表示引用计数(Reference Count),dup、dup2、fork等系统调用会导致多个文件描述符指向同一个file 结构。假如fd1 和fd2 同时指向同一个file 结构,当close(fd1)时,并不会释放file 结构,而只是把引用计数减1,此时如果再close(1),引用计数器减到了0,系统就会释放file 结构,这时才是真正关闭了文件。(见 man 2 close : If fd is the last file descriptor referring to the underlying open file description (see open(2)), the resources associated with the open file description are freed;)

下面这段代码:

int fd = open("/dev/stdout", O_RDWR);
close(1);

stdout = fdopen(fd, "w");
printf("fd = %d\n", fd);
printf("printf hello world\n");
fprintf(stdout, "fprintf hello world\n");

运行结果:
运行结果
因为close(1)后,使用fdopen将stdout 与fd 关联起来,使用strace查看系统调用情况
关于文件描述符--不能不知的几件事_第2张图片
printf(“printf hello world\n”),系统调用直接是往文件描述符为1的里面写,也就是标准输出,但是上面我们使用close(1)将文件描述符1关闭了
printf(“fd = %d\n”,fd) 系统调用是往文件描述符3里面写,跟fprintf(stdout, “fprintf hello world\n”)相同,因为stdout是关联的fd,stdout->_fileno的结果就是fd的值
(这里有一个问题,就是两个printf 系统调用不同???没有明白为什么,可能跟printf的具体实现有关,只打印字符串的话,使用的是puts,如果带可变参数,就不是使用的puts)
也就是说,标准输出这个文件,有两个文件描述符1 和3 同时指向,但是关闭1时,并不会释放该file结构,仍然能够通过文件描述符3往文件中写入数据。

问题解答:
http://bbs.csdn.net/topics/392022793
这也是我之前发的一个帖子。
这是编译器对printf 的一种优化,对于printf的参数如果是以’\n’结束的纯字符串,printf会被优化为puts函数,而字符串的结尾’\n’符号被消除。除此之外,都会正常生成call printf指令。

第一个printf 反汇编结果为callq <_IO_printf>
第二个为callq <_IO_puts>

查看源代码,发现
ldbl_strong_alias (__printf, printf); 我理解的意思就是将printf绑定到符号__printf。
而在__printf 实现中,调用vfprintf (stdout, format, arg);直接使用的就是 stdout。

而在ioputs.c中,
weak_alias (_IO_puts, puts) 将puts绑定到_IO_puts符号

注意:open,write这些 linux中的系统调用都是无缓冲区函数,而printf,fprintf,fopen,fwrite这些库函数都是先将数据放入用户空间的缓冲区中的,所以,如果使用

dup2(fd, 1)
printf("1hello world");
printf("2hello world\n");
write(fd, "hello world111\n", strlen("hello world111\n"));

会发现结果文件中,write的数据先写入,而printf的数据后写入。但是如果使用的是printf("hello world\n"),就会直接写入文件,因为遇到换行这样的,会刷新缓冲区。

以上这只是我的预想结果,但是实际情况就是,write写入的文件结果总是在printf前面,这个不是很理解。但是如果在第二个printf 后面添加 fflush (stdin) 就会立马写入文件,在 write 函数之前就写入文件。

我查了一些资料,说的是,printf 这些函数的缓冲区,遇到这些情况的时候:
1 缓冲区填满
2 写入的字符中有‘\n’ ‘\r’
3 调用fflush手动刷新缓冲区
4 调用scanf要从缓冲区中读取数据时,也会将缓冲区内的数据刷新
以上这些情况的时候,会刷新缓冲区,printf 会立即输出,上文中的代码,遇到回车时,还是在write 后面才写入文件的,只有在 fflush 的时候才会立马写入文件中。

你可能感兴趣的:(学习总结,linux,c/c++,学习心得,指针,linux,linux-c)