计算机系统基础学习心得(其三)——IO相关

终于在这个学期末的时候迎来了第三篇(考试都结束了居然还在写这门课的博客),这次我们来讲I/O相关的内容。说起I/O,相信各位只要学过编码,就绝对不会陌生,因为不管你从哪门编程语言入门,都绝对离不开I/O。我们当初是从C语言开始的,但不知道是没时间了还是因为其他什么原因,我们老师讲到I/O部分就一笔带过了,只稍微提到了一下fopen,fread,fwrite之类,然后稍微提了提缓存流的事。正因为如此,以至于我到今天仍然对I/O抱有一种莫名其妙的感情,总感觉完全不认识它。(第一感觉真的很重要)得亏这学期又好好学了一遍I/O,填上了这片空白.。


碎碎念结束,下面是正题。

在Linux中流传着一句话叫“一切皆文件。”在Linux中所有的I/O设备(例如网络,磁盘,终端)统统都被模型化为文件,而所有的输入输出都被当作相应文件的读和写来执行。这种将设备优雅地映射为文件的方式,允许Linux内核引出一个简单,低级的应用接口,称为Unix I/O,这使得所有的输入和输出都能以一种统一且一致的方式来执行。我们的内容也从这里开始,在Linux中你看到的一切都可以用文件表示,而每一个Linux文件都有一个类型来表明它在系统中的角色:

        普通文件:包含任意数据。应用程序常常还要区分文本文件和二进制文件,文本文件是只含有ASCII或Unicode字符的普通文件,二进制文件则是除文本文件外的所有文件。之所以叫普通文件,大概也就是因为平时见得最多吧,各种能用文本编辑器打开的文件之类的都列在普通文件的范畴内。

        目录文件:包含一组链接的文件,每个链接都将一个文件名映射到一个文件。虽说目录文件说起来是文件,其实它的存储内容只是一张表而已,一张关于文件名和inode号的映射的表,系统通过这个表来明白链接究竟要去向何处。

        其他的还有什么通道文件,链接文件,块文件,字符文件......Linux内核将所有文件都组织成一个目录层次结构,由名为/(斜杠)的根目录确定,系统中每个文件都是根目录的直接或间接的后代,下图展示了Linux系统的目录层次结构的一部分:

现在我们知道了Linux里有这么多的文件了,那么我们当然需要来标识它们了,那么我们怎么标识它们呢?答案是用文件描述符(一个整型数据,操作所有文件),每当你创建一个文件,Linux系统都会自动地给你创建的文件分配一个文件描述符,这个文件描述符总是当前未使用的最小的非负整数,但是就算原本系统里什么文件都没有,你去创建一个文件,Linux也不会为你分配0号,而总是从3号开始,这是因为0,1,2号已经被默认占用了,分别是0—标准输入(stdin),1—标准输出(stdout),2—标准出错(stderr)。

说完了文件的类型和标识,接下来就是各种调用文件的方法了:

        1.打开文件:使用的是open函数,函数的原型如下:

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

int open(char *fileename, int flags, mode_t mode);

          它将filename转换为一个文件描述符,并返回描述符数字。

          flags参数指明进程访问该文件的方式;O_RDONLY 只读;O_WRONLY 只写;O_RDWR 可读可写。

          当然它也可以衍生出更多的内容:

                  1.O_CREAT:如果文件不存在,就创建它的一个截断的(空)文件。

                  2.O_TRUNC:如果文件已经存在,就截断它。(如果打开的文件可读/可读写)

                  3.O_APPEND:在每次写操作前,设置文件位置到文件的结尾处。(追加方式)

           再之后是mode位,它被用来说明文件的权限:

                       

                      举个例子,因为r = 4,w = 2, x = 1,所以如果文件的权限是rwx 就是111 = 4 + 2 + 1 = 7

            2.关闭文件:使用的是close函数,函数原型如下:

#include <unistd.h>

int close(int fd);

             用来关闭一个打开了的文件(用文件描述符来表示已打开的文件)。如果关闭一个已关闭的描述符会发生错误。

           3.读和写文件:通过调用read和write函数来执行输入和输出,函数原型如下:

#include <unistd.h>

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

          read函数从描述符为fd的当前文件位置复制最多n个字节到内存位置buf,错误时返回1,读到文件结尾(EOF)时返回0,            否则返回值表示的是实际传送的字节数量。(这里ssize_t 表示的是有符号的整型,而size_t表示的是无符号的整型)

         write函数则是从内存位置buf复制至多n个字节到描述符fd的当前文件位置。

         (p.s. 需要注意的是这里返回的都是实际达成的字节,因为很可能还没有读完或写完要求的字节就已经读到EOF了)

          4.定位函数:它一般不单独使用,它是用来给fd指定文件定位光标,也就是为读写文件的函数找到读写的位置。原型如下:

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

            它返回新光标位置相较于文件开头的偏移量,所以其第二个参数"offset"是可正可负的,最后一个参数"whence"表示的是定             位的方式:SEEK_SET:文件开头,SEEK_CUK:当前光标的位置,SEEK_END:文件末尾。有了这个函数再加上读写             文件函数,就能很好的实现文件的读写了。

          5.文件的重定向:Linux中可以实现文件的重定向一般是使用dup2函数,原型如下:

#include <unistd.h>

int dup2(int oldfd, int newfd)

             它将复制描述符表表项 oldfd 到描述符表表项 newfd ,覆盖描述符表表项 newfd以前的内容。

             当然,这种重定位是基于共享文件来的:

                      内核是这样来表示已打开的文件的:每个进程都有自己的描述符表(Descriptor table),然后 Descriptor 1 指向终端,                       Descriptor 4 指向磁盘文件,如下图所示:

                     

                    这里有一个需要说明的情况,就是使用 fork。子进程实际上是会继承父进程打开的文件的。在 fork 之后,子进程实                       际上和父进程的指向是一样的,这里需要注意的是会把引用计数加 1,如下图所示:

                    

              这样我们就很容易理解dup2函数的意义了,如下图所示:

               

    最后是C语言标准库里的标准I/O函数,其实它们也就是对之前说的系统函数进行了封装,得以获得更好的可移植性(无论在哪个系统下只要使用C语言,就可以使用这些函数):

  • 打开和关闭文件: fopenfclose
  • 读取和写入字节: freadfwrite
  • 读取和写入行: fgetsfputs
  • 格式化读取和写入: fscanffprintf
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值