基础I/O----通过系统调用来操纵文件

简单回忆一下两个在C语言中用于文件的读写的函数

size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); //用于从文件里边读数据

    ptr:用于存储读取数据的空间

    size:读取元素的大小

    nmemb:读取元素的个数

    stream:读取的那个文件

size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);//用于向文件里边写数据。

    ptr:将要写入的数据

    size:写入元素的大小

    nmemb:写入元素的个数

    stream:进行写入的那个文件

 

以f开头的一系列文件操纵函数fopen()、fwrite()、fread()等都是C标准库为了方便我们的使用而提供的函数,所以我们称之为库函数。库函数是用户级的,并不是操作系统为我们提供的。事实上,很多库函数就是将操作系统中为我们提供的一些系统调用接口进行了封装,让我们使用起来更加便捷。当然并不是所有的库函数都是封装了系统调用。可能有小伙伴会奇怪,既然库函数也调用了系统调用,也会有用户态到内核态之间的切换,为什么不直接调用系统调用呢?我们都知道用户态与内核态之间的切换的开销是很大的,库函数的出现(包含系统调用的库函数),就是为了减少用户态到内核态的切换的。库函数往往提供了缓冲区机制,这就可以保证将多次系统调用放到一起来执行。从而减少用户态与内核态之间的切换次数。

自我感觉文件操作一项是比较麻烦的,问了解决这个麻烦,今天来更深层次的来理解文件操作。

接下来学习一系列新的用于操纵文件的函数,由操作系统为我们提供的系统调用接口。

 

open系统调用 ---用于打开一个文件并返回该文件的文件描述符

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

参数:pathname是打开文件的文件名(或者称为路径。事实上文件名就是一种路径)

          flags用于指定文件打开的方式,它有固定的值。

          mode用于表示创建文件的权限。

返回值:调用成功则返回该文件的文件描述符(实质就是一个非负整数),失败则返回-1.

说明:参数flags的取值的定义在头文件<fcntl.h>中,常用的有如下几个取值。

O_RDONLY:只读的方式

O_WRONLY:只写的方式

O_RDWR:读写的方式

以上三种方式必须选且只选一个(前提是:文件已经存在了),下边的参数是可选的。在这里只介绍一些常用的

O_CREAT:如果文件不存在则创建

O_EXCL:要打开的文件如果存在则出错。通常与O_CREAT联合使用(O_CREAT | O_EXCL),两者联合起来使用保证创建的是一个全新的文件。

O_APPEND:但是以后每次写文件时都会先将当前文件偏移量设置到文件末尾,从而保证每次都从文件末尾开始接着写读文件不受影响

O_TRUNC:打开文件的同时将文件内容清空。

 

注意:O_CREAT 创建-----涉及到文件的权限

如果第二个参数使用了O_CREAT,那就必须给第三个参数mode一组权限 如0644(这是文件权限的数字表示方法)

如果没有给权限给mode 系统是不会报错的 但是打开的文件的权限通常是有问题的---就是个垃圾

 

例子:

如果我们不确定这个文件是否已经存在,可以将第二个参数改为 O_WRONLY|O_CREAT|O_EXCL,该组合参数可保证创建一个全新的文件,并且以只读的方式打开。

 

close系统调用 ----用于关闭文件描述符

原型:int close ( int fd )

参数就是所要关闭的文件的文件描述符(open函数的返回值)

成功返回0 ,失败返回-1。

 

write函数 ---向指定文件中写入数据

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

参数:fd是文件描述符,代表某个文件

         buf是要写入文件的内容的起始地址

         count是写入内容的长度

调用成功返回值代表实际写入的字符的实际长度  返回为0表示一个都没有写入。调用失败则返回-1.

注意:write不会将源文件里边的内容清空 而是从头开始写,将原来的内容覆盖掉,而其他的不变

与C语言中的fwrite不一样

C语言中会将原来的内容都清空掉,因为C语言在调用write函数时添加了O_TRUNC选项 ,而O_TRUNC是截断的意思

 

PS:使用命令 wc -c mmc.c 来统计文件 mmc.c中的字符个数

       可以使用命令 echo "abcdefghijklmn..." > mmc.c 将内容"abcdefghijklmn..."写到文件mmc.c中去

 

read函数 ---用于从文件里边读取数据

函数原型:sszie_t read(int fd , void* buf , size_t size);

参数:buf 是指要将文件fd里边的内容读到buf这块空间里 

         size是准备写入buf的大小

返回实际读取的字符个数-----已经成功拿到应用缓存中去了的个数。

 

文件描述符fd

前边在将open函数的时候就提到了,返回值是打开文件的文件描述符,现在来正式介绍一下文件描述符。

文件描述符就是从0开始的整数。

当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,这个数据结构就是file结构体。利用file结构体来表示一个已经打开的文件对象。

而进程执行open系统调用打开一个文件,什么叫打开一个文件呢?就是能访问到那个文件,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向用于描述打开文件的结构体的指针!所以,本质上,文件描述符就是该数组的下标。新打开一个文件,就在这个表里边从头开始找一个未使用的位置,将用于描述新打开的文件的结构体的地址存储在里边,然后将这个位置的下标返回。

只要拿着文件描述符(下标),马上就能得到对应的描述文件的结构体的地址,地址有了找到文件也就是理所当然的事了。

几个特殊的文件描述符,这几个是默认打开的。

0标准输入 -----键盘

1标准输出 -----屏幕

2标准出错 -----屏幕

文件描述符数组下标,数组的元素个数是有限的,那么文件描述符也是有一定的上限。使用命令 ulimit -n 来查看当前文件描述符的上限 

但是这个上限是可以修改的 使用命令ulimit -n 2048 就将上限改为2048了,

修改上限也是有限的 使用命令  cat /proc/sys/fs/file-max  可以查看我们可以修改的上限的最大值----系统里的上限

 

PS:使用命令ulimit -a 查看关于各种资源上限

调整栈空间的大小使用命令 ulimit -s 102400 

 

重定向

       文件描述符的分配规则就是从0开始找到第一个未被使用的整数分配给新打开的文件。而0默认为标准输入,1默认标准输出,2默认为标准出错。知道了这一点,试想一下,如果先将1号文件描述符使用close关闭,那么1号文件描述符就空出来了,那么,如果我们现在再打开一个新的文件,那么,1将会分配给这个新打开的文件,也就是说,现在1号文件描述符(标准输出)对应的就是这个文件了,而不再是屏幕。我们输出的内容也就不会再显示在屏幕上,而是新打开的文件里边去。这就实现了输出的重定向,同样地,也可以通过同样的方式实现输入的重定向。

接下来验证一下,到底是不是这样呢?

 

和想象中一样,标准输入和标准出错的重定向的方式跟这个一样,在此就不一一验证了