(一)文件IO

目录

0.所涉及的OS API

1.文件读写的简单例子

1.1 文件操作三步曲

1.2 open打开文件时,open具体做了哪些事情

2. open函数 

2.1 函数原型

2.2 open函数返回值

2.3 open函数的重点:flags参数 

2.3.1、flags 之 O_RDONLY、O_WRONLY、O_RDWR、O_TRUNC、O_APPEND

2.3.2 flags参数 之 O_CREAT、O_EXCL

2.4 文件描述符

2.5 errno和错误号

3. close、write、read、0/1/2

3.1 close函数

3.2 write

3.3 read 

3.4  0/1/2

有关0/1/2

1)/dev/stdin:标准输入文件

2)/dev/stdout:标准输出文件

3)/dev/stderr:标准出错输出文件

4)STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO

4 lseek函数 

4.1 函数原型和头文件

5. 文件描述符表

task_struct 与 文件描述符表 之间的关系 

关系图

O_APPEND 

O_TRUNC 

共享操作文件 

同一进程(程序),多次open同一个文件

多个进程(程序),共享操作同一个文件    

6、dup和dup2函数

6.1 dup

6.2 dup2

6.3 dup、dup2复制的意义

实现文件共享操作

实现重定位 

7. 总结:文件的共享操作

8. fcntl函数 

9. ioctl

为什么有ioctl这个函数


0.所涉及的OS API

    (1)open函数:打开文件
    
    (2)close函数:关闭文件
    
    (3)read函数:从打开的文件读数据
    
    (4)write函数:向打开的文件写数据

    (5)lseek函数:移动在文件中要读写的位置
    
    (7) dup函数:文件读写位置重定位函数,本来是写到这个文件,重定位后可以写到另一个文件里面
    
    (11)fcntl函数:文件描述符设置函数
    
    (12)ioctl函数:一个特殊的函数

1.文件读写的简单例子

1.1 文件操作三步曲

1.2 open打开文件时,open具体做了哪些事情

1)记录打开文件的信息

(a)程序运行起来后,OS会创建一个task_struct的结构体,记录进程运行时的各种信息,比如所打开文件的相关信息

(b)open将文件成功打开后,在task_struct中又会创建一些结构体(数据结构),用于记录当前进程目前所开文件的信息,后续所有的文件操作,都需要依赖于这些信息,其中就包括指向打开文件的文件描述符。

 

2)open函数会申请一段内存空间(内核缓存),后续读写文件时,用于临时缓存读写文件时的数据

为什么叫内核缓存?

        open是OS所提供的系统函数,属于OS内核的一部分,所以open函数所开辟的缓存空间,就是内核缓存。

open为什么要开内核缓存空间?

        内存读写速度 > 磁盘读写速度的,有了在内存中开辟的内核缓存后,上层读写数据时,会直接读写缓存

注意:open时只是开辟了内核缓存空间,里面并没有数据,只有当进行读写数据时,才会缓存读写的数据。

读写时,缓存间数据的流动是怎样的?

                                        

2. open函数 

2.1 函数原型

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
//功能:只能打开存在的文件,如果文件不存在就返回-1报错。
	 
int open(const char *pathname, int flags, mode_t mode);
//功能:如果文件存在就直接打开,如果文件不存在,就按照mode指定的文件权限,创建一个该名字的新文件。

		
//也就是说三个参数时,不仅包含打开已存在文件的功能,还多了一个创建文件的功能。

2.2 open函数返回值

如果打开成功,返回一个非负整数的文件描述符。
如果打开失败,返回-1,并且设置错误号给系统定义的全局变量errno,用于标记函数到底出了什么错误。

2.3 open函数的重点:flags参数 

参数1:pathname,表示路径名,很简单
参数3:mode,创建文件时,用于指定文件的原始权限,其实就是rwxrwxr--。

2.3.1、flags 之 O_RDONLY、O_WRONLY、O_RDWR、O_TRUNC、O_APPEND

            

2.3.2 flags参数 之 O_CREAT、O_EXCL

(1)O_CREAT

               

(2)O_EXCL

               

2.4 文件描述符

2.4.1 什么是文件描述符

       open成功就会返回一个非负整数(0、1、2、3...)的文件描述符 文件描述符指向了打开的文件

 

2.4.2 文件描述符池

       每个程序运行起来后,就是一个进程,系统会给每个进程分配0~1023的文件描述符范围,也就是说每个进程打开文件时,open所 返回的文件描述符,是在0~1023范围中的某个数字。

 

2.4.3 为什么open返回的是3

        open返回文件描述符是由规则的:规则就是,open返回文件描述符池中,当前最小没用的哪一个。

        进程一运行起来,0/1/2(标准输入输出错误)默认就被使用了,最小没被用的是3,所以返回3。

 

2.4.4 文件关闭后,被文件用的描述符怎么办

      会被释放,等着下一次open时,被重复利用。

 

2.4.5 open的文件描述符 与 fopen的文件指针

2.5 errno和错误号

都被定义在了errno.h头文件,使用errno时需要包含这个头文件。

perror函数

perror函数可以自动将“错误号”换成对应的文字信息,并打印出来,方便我们理解。

perror是一个C库函数,不是一个系统函数。

perror的工作原理?

             调用perror函数时,它会自动去一张对照表,将errno中保存的错误号,换成具体的文字信息并打印出来,我们就能知道函数的具体错误原因了

3. close、write、read、0/1/2

3.1 close函数

close关闭文件时做了什么 

(1)open打开文件时,会在进程的task_struct结构中,创建相应的结构体,以存放打开文件的相关信息。

 

(2)结构体的空间开辟于哪里

       open函数会通过调用类似malloc的函数,在内存中开辟相应的结构体空间,如果文件被关闭,存放该文件被打开时信息的结构体空间,就必须被释放 类似free(空间地址);

       不过malloc和free是给C应用程序调用的库函数,Linux系统内部开辟和释放空间时,用的是自己特有的函数

 

(3)有关task_stuct结构体

    1)这个结构体用于存放进程在运行的过程中,所涉及到的各种信息,其中就包括进程所打开文件的相关信息。

    2)task_stuct结构体,什么时候开辟的?

              进程(程序)开始运行时,由Linux系统调用自己的系统函数,在内存中开辟的

    3)什么时候释放

             进程运行结束了,Linux系统会调用自己的系统函数,释放这个结构体空间

3.2 write

3.3 read 

3.4  0/1/2

有关0/1/2

(1)程序开始运行时,有三个文件被自动打开了,打开时分别使用了这三个文件描述符。
(2)依次打开的三个文件分别是     /dev/stdin,/dev/stdout,/dev/stderr。

1)/dev/stdin:标准输入文件

(a)程序开始运行时,默认会调用open("/dev/stdin", O_RDONLY)将其打开,返回的文件描述符是0

(b)使用0这个文件描述符,可以从键盘输入的数据

(c)思考:read(0, buf, sizeof(buf))实现的是什么功能

          

(d)思考:为什么在我们的程序中,默认就能使用scanf函数从键盘输入数据

          

- 直接使用read的缺点

             所有从键盘输入的都是字符,从键盘输入100,其实输入的是三个字符'1'、'0'、'0',

             因此使用read函数从键盘读数据时,读到的永远都是字符。

2)/dev/stdout:标准输出文件

(a)程序开始运行时,默认open("/dev/stdout", O_WRONLY)将其打开,返回的文件描述符是1

(b)通过1这个文件描述符,可以将数据写(打印)到屏幕上显示

(c)思考:write(1, buf, strlen(buf))实现的是什么功能

        

(d)思考:为什么在我们的程序中,默认就能使用printf打印数据到显示器

        

(e)思考:如何使用write函数,将整数65输出到屏幕显示?

        

           人只看得懂字符,所以所有输出到屏幕显示的,都必须转成字符。

           所以我们输出时,输出的必须是文字编码,显示时会自动将文字编码翻译为字符图形。

           所以我们输出65时,65解读为A字符的ASCII编码,编码被翻译后的图形自然就是A。
 

3)/dev/stderr:标准出错输出文件

(a)默认open("/dev/stderr", O_WRONLY)将其打开,返回的文件描述符是2

(b)通过2这个文件描述符,可以将报错信息写(打印)到屏幕上显示

(c)思考:write(2, buf, sizeof(buf))实现的是什么功能

       

(d)疑问:1和2啥区别?

       

4)STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO

       

4 lseek函数 

4.1 函数原型和头文件

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

  

 

5. 文件描述符表

task_struct 与 文件描述符表 之间的关系 

关系图

(1)0/1/2默认被使用了。

(2)文件状态标志

       

(3)文件位移量 与 文件长度

       

(3)函数指针

       

 ( 4 ) i节点信息 

       缓存在内存的文件属性信息

O_APPEND 

O_TRUNC 

如果文件中原来就有数据的话,open打开文件时,会全部被清空。
        
由于文件已经被清空了,所以将V节点中的文件长度,修改为0

共享操作文件 

同一进程(程序),多次open同一个文件

(1)在同一个进程中多次open打开同一文件时,文件描述符可能会相同吗?

    不可能。

(2)写数据时,为什么会相互的覆盖?

     1)看看共享操作时的文件描述符表长啥样

               

      正是由于不同的文件描述符,各自对应一个独立的文件表,在文件表中有属于自己的“文件位移量”,开始时都是0。

   2)怎么解决

      (a)指定O_APPEND即可解决

      (b)为什么使用O_APPEND可以解决

           

多个进程(程序),共享操作同一个文件    

不同进程打开同一文件时,各自使用的文件描述符值可能相等 

(2)进程表 和 文件描述符表

            

(3)覆盖的原因

            也是因为因为各自有独立的文件位移量。

(4)解决办法

            指定O_APPEND标志,写操作时,使用文件长度去更新文件位移量,保证各自操作时,都在文件的尾部操作

6、dup和dup2函数

6.1 dup

函数原型

#include <unistd.h>	
int dup(int oldfd);

   

6.2 dup2

#include <unistd.h>
int dup2(int oldfd, int newfd);

  

6.3 dup、dup2复制的意义

实现文件共享操作

为什么没有覆盖?

     使用dup、dup2复制方式实现文件共享时,不管复制出多少个文件描述符,它们永远只有一个文件表,

     所以使用所有描述符去操作文件时,最后使用的都是通过同一个文件位移量

     不管谁操作后文件位移量都会被更新,因此不会出现覆盖。
 

实现重定位 

(1) 什么是重定位

         所谓重定位,说白了就是,文件描述符所指向的文件该变了,使得数据输出的目标文件也随之变。

 

(2) 举例使用dup、dup2实现重定位: printf输出到file.txt文件

                

(3) 什么时候会使用dup、dup2,来实现从定位 

            函数中的文件描述符值写死了,无法修改为新的描述符,但是你又希望该函数,把数据输出到其它文件中,

            此时就可以使用dup、dup2对该函数中的文件描述符,进行重定位,指向新的文件,就会将数据输出到新文件

(4) 重定位 > 

                 重定位 命令(>)是dup、dup2的典型应用,这个命令在重定位时,就是调用dup、dup2函数来实现的。

          具体从定位的过程:  ls > file.txt

               

7. 总结:文件的共享操作

(1)单一进程多次open同一个文件,实现共享操作

(2)多个进程多次open,共享操作同一个文件

(3)在单个进程中,使用dup、dup2实现文件共享操作

8. fcntl函数 

 函数原型

#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd, ... /* arg */ );

  

9. ioctl

为什么有ioctl这个函数

    在某些特殊的情况下,使用read、write、lseek函数进行文件io(读写)操作时,存在一定的问题,

    此时往往就是使用ioctl函数,来实现这些比较特殊情况的io操作。

    ioctl是一个杂物箱,根据设置参数的不同,有很多种不同的功能,需要具体场景具体分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值