系统章节-----基础IO

目录

1.复习下C的文件接口

fopen:打开文件    FILE *fopen(const char *path,const char *mode)

fwrite:向文件当中写

fread:从文件当中读

fseek:移动文件流指针的位置

int fclose(FILE*fp);关闭文件

2.系统调用的文件接口

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

write:写

read:读

lseek:移动文件流指针

close:

3.文件描述符

(1)文件描述符:文件描述符就是一个小正数(没有负数)

(2)打印文件描述符的值

(3)文件描述符的分配规则:最小未使用

4.内核理解文件描述符

从task_struct的角度理解文件描述符是什么?通过查看源码可知:

面试题:一个进程最多打开多少个文件描述符 ?

5.理解文件描述符和文件流指针的区别

(1)从源码的角度理解,文件流指针(struct  _IO_FILE)是什么?

 (2)将文件描述符和文件流指针结合起来

6.重定向

(1)重定向的符号

(2)重定向的接口

(3)重定向代码

 7.静态库&动态库

(1)动态库

(2)静态库

8.软硬链接

9.文件系统

ext2文件系统:


1.复习下C的文件接口

fopen:打开文件
    FILE *fopen(const char *path,const char *mode)

path:带有路径的文件名称(如果说不带有路径,打开的文件可以是在当前路径下)

mode:打开文件的方式

下表为一些常见的mode

文件使用方式 含义如果指定文件不存在
“r”(只读) 为了输入数据,打开一个已经存在的文本文件出错
“w” (只写)
为了输出数据,打开一个文本文件 (输出的时候会将之前的内容清空)建立一个新的文件
“a”(追加)向文本文件尾添加数据 出错
“rb”(只读)为了输入数据,打开一个二进制文件 出错
“wb”(只写)为了输出数据,打开一个二进制文件 建立一个新的文件
ab”(追加)向一个二进制文件尾添加数据 出错
“r+”(读写)为了读和写,打开一个文本文件出错
“w+”(读写)为了读和写,建议一个新的文件 建立一个新的文件
“a+”(读写)打开一个文件,在文件尾进行读写 建立一个新的文件
“rb+”(读写)为了读和写打开一个二进制文件出错
“wb+”(读写) 为了读和写,新建一个新的二进制文件 建立一个新的文件
“ab+”(读写)打开一个二进制文件,在文件尾进行读和写建立一个新的文件

fwrite:向文件当中写

size_t fwrite(const void*ptr,size_t size,size_t nmemb,FILE *stream);

参数:ptr:要往文件中写的内容

           size:定义往文件中写的时候,一个块是多大,单位字节;(通常定义为一个字节)

           nmemb:期望写多少块

           stream:文件流指针

返回值:返回成功写入到文件的块数

通常情况下,代码调用时,这样设置:

size:1(1个块的大小是1字节)

nmemb:块的个数(期望写多少字节)

返回值:成功写入文件当中块的个数(成功写入文件中的字节数量)


fread:从文件当中读

size_t fread(void *ptr,size_t size,size_t nmemb,FILE*stream)

参数:ptr:将文件中的内容保存到ptr指向的内存空间当中(这个内存空间一定要是合法的(程序员自己定义的))

           size:定义从文件中读的时候,一个块是多大,单位字节;(通常定义为一个字节)

           nmemb:期望读多少块

           stream:文件流指针

返回值:返回成功读入的文件块的个数


fseek:移动文件流指针的位置

参数:stream:文件流指针

           offset:偏移量

           whence:将文件流指针偏移到什么位置

                SEEK_SET:文件头部

                SEEK_CUR:当前文件流指针的位置

                SEEK_END:文件末尾

返回值:成功:0

              失败:-1


int fclose(FILE*fp);关闭文件

fp:文件流指针

作用:关闭文件流(关闭文件)

注意:打开的文件使用完成之后一定要记得关闭文件,否则

当文件句柄持续泄露的时候,最终进程就不能打开新的文件了;内存泄漏,导致进程占用的内存资源过多,如果持续泄漏,操作系统是没有办法满足进程申请内存资源的请求,操作系统就会强杀进程


2.系统调用的文件接口

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

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

参数:pathname:要打开或创建的文件目录

           flags:打开文件时可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。

                O_RDONLY:只读打开

                O_WRONLY:只写打开

                O_RDWR:读写打开

        这三个常量,必须且只能指定一个

                O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明文件的访问权限

                O_APPEND:追加写

举一个例子:O_RDWR | O_CREAT:以可读可写的方式打开文件,若文件不存在则创建它。

             mode:当创建一个新文件的时候指定新创建文件的权限;传递一个8进制的数字(就是权限,例如0664)

返回值:成功:新打开的文件描述符

               失败:-1


write:写

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

参数:fd:文件描述符

           buf:将buf指向的内容写到文件当中去

           count:期望写多少字节

返回值:返回写入的字节数量


read:读

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

参数:fd:文件描述符

           buf:将文件当中读到的内容写到buf指向的空间当中去

           count:期望读多少字节

返回值:返回读到的字节数量


lseek:移动文件流指针

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

参数:fd:文件描述符

           offset:偏移量,字节单位

           whence:偏移的位置

                SEEK_SET:文件头部

                SEEK_CUR:当前文件流指针的位置

                SEEK_END:文件末尾

返回值:  成功:返回偏移的位置,单位字节

                失败:返回-1


close:

int close(int fd);

关闭文件描述符


3.文件描述符

(1)文件描述符:文件描述符就是一个小正数(没有负数)

(2)打印文件描述符的值

(3)文件描述符的分配规则:最小未使用

文件描述符不是固定进行分配,而是按照最小未使用原则进行分配的

由上图可知:当我们关闭0号文件描述符的时候,系统给我们fd分配的就是0

4.内核理解文件描述符

从task_struct的角度理解文件描述符是什么?
通过查看源码可知:

当我们创建一个进程的时候就会产生一个task_struct结构体,task_struct结构体中含有一个files_struct结构体指针,指向一个files_struct结构体,在这个files_struct结构体中有一个fd_array数组,文件描述符就是fd_array这个数组的下标。

面试题:一个进程最多打开多少个文件描述符 ?

这个问题的场景并不是在基础IO(面试官和你探讨文件操作的时候问道的),而大多数场景都是和面试官在讨论网络编程的时候,会被问到的。

  1. 系统当中针对一个进程打开的文件描述符的数量是有限制的。
  2. 通过ulimit -a可以查看到“open files”的大小,而“open files”的大小就是一个进程可以打开的文件描述符的数量。
  3. 我的机器中看到的“open files”的大小是1024/100001,也就是意味着,我的机器创建出来的进程最大打开的文件数量就是1024/100001
  4. “open files”的大小并不是没有办法改变的,是可以通过ulimit -n[count]进行改变。

5.理解文件描述符和文件流指针的区别

(1)从源码的角度理解,文件流指针(struct  _IO_FILE)是什么?

文件流指针FILE*及struct _IO_FILE是一个由C标准库定义的结构体,这个结构体中的一个int _filen元素保存文件描述符的数值。

这个结构体数组中含有我们在exit函数执行的时候清理缓冲区时提到的那个缓冲区

现在就知道为什么_exit函数不清理缓冲区吗?

因为缓冲区是C标准库定义的,而_exit函数是系统调用函数,当然不会清理缓冲区了。

 (2)将文件描述符和文件流指针结合起来

文件流指针对应的结构体 struct _IO_FILE这个结构体内部的成员变量 int _fileno保存了对应的文件描述符数值。

总结:文件流指针是标准库操作文件句柄,文件描述符是系统调用接口。

           文件流指针中包含了一个成员变量,这个成员变量中保存的就是文件描述符。

           库函数内部封装的就是系统调用函数。

           缓冲区是由C标准库定义的,是用户态的缓冲区,存在于文件流指针的结构体中。

6.重定向

(1)重定向的符号

        >:清空重定向

        >>:追加重定向

(2)重定向的接口

        int dup2(int oldfd,int newfd);

        作用:将newfd的值重定向为oldfd,既newfd拷贝oldfd

        参数:oldfd/newfd均是文件描述符

        成功:1.关闭newfd

                   2.让newfd指向oldfd对应的struct file*结构体

        失败:1.如果oldfd是一个非法/无效的文件描述符,则重定向失败;newfd没有变化

                   2.如果oldfd和newfd的值相等,则什么事都没干

(3)重定向代码

重定向标准输出到1.txt文件当中

 7.静态库&动态库

什么是库:静态库和动态库都是程序代码的集合。一般是为了方便将程序提供给第三方使用,就是将程序编写成二进制库文件提供给第三方(用户)使用调用着不必关心内部实现,只需要关心如何使用(如何调用)即可

(1)动态库

特征:win:没有前缀,后缀为dll

           linux:前缀为lib,后缀为.so

生成:使用gcc/g++编译器,增加两个命令行参数

                -fPIC

                -shared

生成动态库的代码中不需要包含main函数

使用:编译可执行程序的时候,依赖动态库

        -L [path] :指定动态库所在的路径

        -l[动态库的名称(去掉前缀和后缀之后的名称)]:指定编译可执行程序时,依赖的动态库。

都有哪些方式让程序可以找到动态库:

        1.将动态库放到可执行程序的路径下(不推荐)

        2.配置LD_LIBBARY_PATH

                动态库的环境变量:LD_LIBBARY_PATH(推荐的方式,公式中在客户机器部署的时候,常用的手段)

        3.放到系统库的路径下:/lib64

(2)静态库

        特征:win没有前缀,后缀为.lib

                   linux:前缀为lib,后缀为.a

        生成:第一阶段:使用gcc/g++将源代码编译成为目标程序(.o)

                   第二阶段:使用ar -rc 命令编译目标程序成为静态库

                        ar -rc [静态库文件名称] [目标文件]

        注意:如果直接使用源码时不行的

8.软硬链接

        软连接:目标文件的快捷方式

    

生成:ln -s 源文件 软连接文件

注意事项:

  1. 修改软连接文件,源文件也会被修改
  2. 源文件如果被删除,软连接文件还在的,修改软连接文件,会重新建立源文件,重新建立链接关系(这种要慎重)一定要在删除源文件的时候,将软链接文件也删除掉

 硬链接:目标文件的替身

     生成:ln 源文件 硬链接文件

9.文件系统

我们使用 ls -l 的时候看到的除了看到文件名,还看到了文件元数据

stat可以看到更多信息

ext2文件系统:

 Linux ext2文件系统,上图为磁盘文件系统图(内核内存映像肯定有所不同),磁盘是典型的块设备,硬盘分区被 划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。例如mke2fs-b选项可以设 block大小为102420484096字节。而上图中启动块(Boot Block)的大小是确定的。

  • Block Groupext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。政府管理各区的例子
  • 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck inode的总量,未使用的blockinode的数量,一个blockinode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。SuperBlock的信息被破坏,可以说整个文件系统结构就被破坏了
  • GDTGroup Descriptor Table:块组描述符,描述块组属性信息,有兴趣的同学可以在了解一下
    块位图( Block Bitmap ): Block Bitmap 中记录着 Data Block 中哪个数据块已经被占用,哪个数据块没
    有被占用
  • inode 位图( inode Bitmap ):每个 bit 表示一个 inode 是否空闲可用。
  • i 节点表 : 存放文件属性 如 文件大小,所有者,最近修改时间等
  • 数据区:存放文件内容
将属性和数据分开存放的想法看起来很简单,但实际上是如何工作的呢?我们通过 touch 一个新文件来看看如何工作。
[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
263466 abc
为了说明问题,我们将上图简化:
创建一个新文件主要有一下 4 个操作:
1. 存储属性
内核先找到一个空闲的 i 节点(这里是 263466 )。内核把文件信息记录到其中。
2. 存储数据
该文件需要存储在三个磁盘块,内核找到了三个空闲块: 300,500 800 。将内核缓冲区的第一块数据复制到 300 ,下一块复制到 500 ,以此类推。
3. 记录分配情况
文件内容按顺序 300,500,800 存放。内核在 inode 上的磁盘分布区记录了上述块列表。
4. 添加文件名到目录
新的文件名 abc linux 如何在当前的目录中记录这个文件?内核将入口( 263466 abc )添加到目录文件。文件名和 inode 之间的对应关系将文件名和文件的内容及属性连接起来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值