基础IO与文件描述符

来源:互联网 发布:ubuntu 调整时区 编辑:程序博客网 时间:2024/05/24 11:13

一、引题

       之前我们讲过C标准库提供的IO函数fread,fwrite等,那么它们到底是怎么实现的呢?它们是真的靠自己写出来的吗?不一定?

       我们先来看看关于操作系统的概念图




       从中我们可以看出C标准库属于用户部分,是给用户的操作接口,不过这个接口它又是来源于系统调用接口,可以看成他是系统调用的封装,那我们探究一下我们之前提到的c标准库给我们提供的函数fopen(),它封装了那个系统调用的那个接口呢?百度之后发现它其实封装的是open(),我们在Linux下man open,我们发现接口的原型有两种:

 int open(const char *pathname, int flags);

功能:打开和创建文件

参数:pathname:待打开/创建文件的路径名 flags:打开模式()

O_RDONLY只读模式

O_WRONLY只写模式

O_RDWR读写模式

以上三种常量必须选一个,下面的是非必须选,下面这些和上面这些或(|)起来构成完整的打开模式

O_APPEND每次写操作都写入文件的末尾

O_CREAT如果指定文件不存在,则创建这个文件

O_EXCL如果要创建的文件已存在,则返回-1,并且修改errno的值

O_TRUNC如果文件存在,并且以只写/读写方式打开,则清空文件全部内容(即将其长度截短为0)

O_NOCTTY如果路径名指向终端设备,不要把这个设备用作控制终端。

O_NONBLOCK如果路径名指向FIFO/块文件/字符文件,则把文件的打开和后继I/O

返回值:成功则返回文件描述符,否则返回-1

int open(const char *pathname, int flags, mode_t mode);

这个与上面基本一致,只是多了个标志位 这个标志位是用来创立新文件时候,给新文件定各个用户权限的

mode 是一个是一个无符号八进制数,为什么呢,这个就要和chmod联系起来,我们当初设置权限数字的每一位代表一类权限。用户所获得的权限是加权数值的总和。例如764表示所有者拥有读、写和执行权限,群组拥有读和写权限,其他用户拥有读权限。所以同样的,这里的mode也是同样的意思。


ssize_t read(int fd, void *buf, size_t count);

功能:从文件中读数据

参数:fd:文件描述符  buf:保存数据的起始地址  count:指定的字节数

返回值:成功返回读文档的字节数,失败返回-1,当读到EOF,返回0


ssize_t write(int fd, const void *buf, size_t count);

功能:从打开文件中读数据

参数:fd:文件描述符  buf:指定缓冲区  count:指定的字节数

返回值:成功返回写文档的字节数,失败返回-1

By:注意write和read返回值类型是有符号长整型,这样才可以返回负数,达到报错的目的



二、文件描述符

运行环境:gcc (GCC) 4.4.7 

代码:

#include <stdio.h>#include <string.h>#include <fcntl.h>#include <unistd.h>//fd连续分配void Test1(){umask(0);int fd1 = open("./myfile1",O_RDONLY|O_CREAT,0666);if(fd1==-1)printf("open Error\n");    int fd2 = open("./myfile1",O_RDONLY|O_CREAT,0666);    if(fd2==-1)printf("open Error\n");    printf("fd1 = %d fd2 = %d\n",fd1,fd2);    close(fd1);    close(fd2);}//fd从最小的开始分配void Test2(){umask(0);int fd1 = open("./myfile1",O_RDONLY|O_CREAT,0666);if(fd1==-1)printf("open Error\n");printf("fd1 = %d\n",fd1);close(fd1);    int fd2 = open("./myfile2",O_RDONLY|O_CREAT,0666);    if(fd2==-1)printf("open Error\n");    printf("fd2 = %d\n",fd2);    close(fd2);}int main(){    //Test1();    Test2();    return 0;}

Test1()运行结果:

fd1 = 3 fd2 = 4

Test2()运行结果:

fd1 = 3
fd2 = 3

       从这两个运行结果我们可以看出,fd按照从小到大依次分配下标,总是选择在当前情况下最小的一个下标进行分配,作为新的文件描述符,但是我们又有了新的问题,为什么从3开始分配呢?那0,1,2去干嘛了,经过查找,原来0,1,2分别对应着标准输入,标准输出,标准错误,Linux进程默认情况下会打开的文件描述符既然有了这个,那我们就可以利用这种特性改善过去的输出和输入


#include <stdio.h>#include <fcntl.h>#include <string.h>int main(){char* buf ="hello fd!";write(1, buf, strlen(buf));write(2, buf, strlen(buf));return 0;}

运行结果:

hello fd!

    通过这里,我们发现利用这种文件描述符,也可以完成向屏幕输入和打印的功能

#include <stdio.h>int main(){close(1);int fd = open("./test.txt",O_RDONLY|O_CREAT);if(fd==-1){printf("Error\n");}printf("fd = %d\n",fd);close(fd);return 0;}:


运行结果:

fd = 1

       顺便补充下,当我们关闭0,依然符合我们之前总结的,从第一个空闲的最小的下标开始分配的规则。并且最为关键的是实现了重定向,这下相当于之前向屏幕输出的数据,以后都会输出到我们所创建的这个test.txt文件中

注意事项:

open()过后,一定要记得close(),否则会造成资源泄漏,为什么呢,这就需要谈到文件描述符,我们首先来看下面这张图,了解下fd存在意义



       我们将文件描述符简称为fd,从这张图,我们可以看出fd其实是fd_array这个数组的下标,在一般情况下,进程默认打开标准输入,标准输出,标准错误,它们的下标分别是0,1,2,之后的下标是为我们预留,供用户使用的,如果我们不close,那么fd_array数组就会使用完,导致资源泄露。



总结下:从这个文件描述符也让我们了解了Linux的重要思想,一切皆文件,不管是普通文件,目录,字符设备,块设备,套接字,在Linux中都是以文件被对待,它们虽然类型不同,但是对其提供的确实同一套操作模式





原创粉丝点击