Linux学习之旅(14)-----文件I/O

C中文件I/O操作和系统文件I/O的关系?

在C/C++中我们也学过关于文件的操作函数,例如:fopen(),fclose()等。其实Linux中的文件I/O和这些也差不多,那为什么还要学习Linux的文件I/O?那这两者有什么联系和关系那?为什么我们使用printf()函数就可以将一句话打印在显示器上?

当我们使用printf()函数时,实际调用的是操作系统的printf()函数(应用层,这实际就是我们要学习的文件I/O)。然后由操作系统的printf()函数取调用操作系统的sys_printf()(内核层)(假如叫这个名字,这里只讲述原理,不过于深究)。然后内核的函数负责取调用驱动设备,由驱动设备负责将函数的内容打印到显示器上。所以,我们在无形中已经使用过了系统的文件I/O。

我们知道受用c或者c++编写的程序,假如你是在windows系统下生成的exe文件,那么这个exe文件直接拿到Linux系统是不能运行的,我们需要将源代码在linux系统下重新编译链接再一次生成可执行文件才可以。这就是因为上面的说的,C中的函数并不是去直接操作操作系统的底部,而只是去调用应用层的函数,而两个系统下的函数又设计的不相同,所以就不能直接使用。

那既然c提供的函数拥有这些功能(虽然跨平台需要重新编译,但编译也不是很麻烦)那为什么还要学习Liunx系统的文件I/O?这是因为系统提供的文件I/O远比c语言提供的函数功能更强大。举一个列子:

我们知道是使用c语言提供的函数操作文件,比如将一段文件写入文件中,它并不会直接将文件的内容写入磁盘,而是将内容写入文件缓冲区中(每个文件指针都有一个文件缓冲区,大小为8192B,这样做的主要目的是为了减少文件在磁盘上的操作次数,从而提高速度。)等到刷新文件缓冲区的时候再将文件写入磁盘中。也就是如果使用c标准函数将文件写入磁盘中,如果缓冲区没有刷新,那我们是无法读到文件的内容的,虽然你写了,这时你唯一的方法就是等待系统刷新文件缓冲区(也可以手动的刷新)。但是如果使用的系统的函数,就不会出现这样的问题。如果使用系统的AP将文件存入磁盘中,系统的API会去调度内核的层API,将文件内容写在内核的缓冲(守护进程控制)中,虽然也是写在缓冲中,但文件读取是同样也是调用内核的API,就会直接在内核的缓冲中找到文件内容。

刚才提到文件缓冲区的刷新,那么文件刷新都有什么方法那?

(1)缓冲区满。

(2)在内容中遇到了"\n",即换行。作用是将光标移动到输出设备中下一行开头处,并且清空缓冲区。

(3)使用flush手动刷新。将内容输出到设备。

(4)关闭文件。

PCB(Process Control Block)进程控制块

一段代码没有运行之前被成为程序,一旦运行它就是进程(一个程序可以有多个进程)。而PCB是进程存在的唯一标志,系统利用PCB来描述进程的基本情况和活动情况,进而控制和管理进程。一般一个进程(进程实体,进程映像)由三部分组分程序段、数据、PCB。创建进程就是创建了一个PCB,撤销进程也是撤销了一个进程的PCB。对于进程的定义有很多,我们选取了一种比较典型的:

(1)进程是程序的一次执行。

(2)进程是一个程序以及数据在处理机上顺序执行时所发生的活动。

(3)进程是具有独立功能的程序在一个数据集合上运行的过程,它是系统进行资源分配和调度的一个独立单位。

进程具有:动态性、并发行、独立性、异步性。

为什么要在这里谈一下进程那?

因为在系统的文件I/O中会使用到进程。一个程序系统会给它分配4G的空间,用来存储信息。在这4G的存储空间中。其中0-3G是程序单独使用,会不干扰的,而3-4G是内核拥有的,是共享的。系统会在这里为每个进程创建PCB,PCB中保存这这个程序的文件信息。我们都知道在使用文件操作打开一个文件时,打开成功时,就是返回一个文件描述符(关闭时也返回文件描述符,只有打开错误时返回-1),我们可以通过这个文件描述符对文件进程操作。那这个文件描述符是如何找到对应的文件的?就是使用函数返回的文件描述符和PCB中存储的文件信息的文件描述符做比较。其实PCB的文件信息保存在一个名为file struct的结构体中,那时如何存储的那,其实就是一个整形数组(相当于指针的作用,索引)。一个程序在运行时会默认打开三个文件(无论你是否进行文件操作)他们分别是:标准输入、标准输出、标准出错,分别对应的整形数组的0,1,2号位置。如果我们需要打开文件,那该文件的索引就会被存储在3号的位置(分配原则为没有使用的最小的号码。)

打开、关闭文件

在Linux系统中的打开文件的函数为open()函数。

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

可以看到open函数有俩中重载形式。

参数说明:

必选项:必须选择且只能选择一个。

(1)O_RDONLY:只读打开

(2)O_WRONLY:只写打开

(3)O_RDWR:可读可写打开

附加选项(可以选择0个或者多个,需要和必选项按位或):

(1)O_APPEND:追加

(2)O_CREAT:如果文件不存在则创建它,使用时需要提供mode(第三个参数)来表示文件的访问权限。

(3)O_EXCL:如果同时提供了O_CREAT,并且文件存在,则出错返回。

(4)O_TRUNC:如果文件存在,并且以只写或可读可写的方式打开,则将其文件长度截断为0,即重新写的意思。

(5)O_NONBLOCK:对于设备文件,以本方式打开可以作为非阻塞I/O。

read(int fd,void* buf,size_t size);
//成功返回读取到的字节数,读到文件末尾返回0,出错返回-1.
write(int fd,coid* buf,size_t size);
//成返回写入的字节数,出错返回-1并设置errno

创建一个不存在的文件并向其中写入“白日依山尽”。

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

int main()
{
    int fileDes;
    //创建一个使用权限为774的名为test1的文件。
    fileDes=open("test1",O_WRONLY|O_CREAT,0774);
    char buf[]="白日依山尽\n";
    write(fileDes,buf,sizeof(buf)/sizeof(buf[0]));
    close(fileDes);
    return 0;
}

编写程序在刚才的test1文件内加入“黄河入海流”,追加在“白日依山尽”之后。

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

int main()
{
    int fileDes;
    fileDes=open("test1",O_WRONLY|O_APPEND);
    char buf[]="黄河入海流\n";
    write(fileDes,buf,sizeof(buf)/sizeof(buf[0]));
    close(fileDes);
    return 0;
}

创建一个名为test1的文件并且使用O_EXCL参数。

#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
    int fileDes;
    fileDes=open("test1",O_WRONLY|O_CREAT|O_EXCL,0777);
    //没打开就输出
    if(fileDes==-1)
    {
        printf("对应文件以存在\n");
    }   
    //打开就写入
    else
    {   
        char buf[]="欲穷千里目\n";
        write(fileDes,buf,sizeof(buf)/sizeof(buf[0]));
        close(fileDes);
    }   
    return 0;
}

使用O_TRUNC将“更上一层楼”写入文件中(加入这个参数,会将原来的文件内容全部删除,然后重新开始写)。

#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fileDes;
    fileDes=open("test1",O_WRONLY|O_TRUNC);
    char buf[]="更上一层楼\n";
    write(fileDes,buf,sizeof(buf)/sizeof(buf[0]));
    close(fileDes);
    return 0;
}

如果不加O_TRUNC会如何那,我们直接在test1文件中写入“孟浩然”。

#include <fcntl.h>
#include <unistd.h>

int main()
{
    int fileDes;
    fileDes=open("test1",O_WRONLY);
    char buf[]="孟浩然";
    write(fileDes,buf,sizeof(buf)/sizeof(buf[0]));
    close(fileDes);
    return 0;
}

我们可以到如果不加O_TRUNC,会从文件开头开始写,覆盖之前的内容。如果新写入的内容的字节数大于原文件,那不会产生什么影响,一旦新输入的内容的字节数小于原文件就会出现下面的情况。

乱码的原因:我们知道一个汉字占两个字符,而字符串会在末尾自动加上'\0',会占用一个字节,这样就会把“一”字的一半覆盖,那一半就会显示乱码。

使用系统API简单实现cp命令功能。

我们知道cp命令的作用为复制文件或目录。这里我们只实现复制文件。

include <fcntl.h>
#include <unistd.h>
#include <stdio.h>

int main(int args,char* argv[])
{
    //说明缺少参数
    if(args<3)
    {
        printf("请检查文件名个数\n");
        exit(1);
    }
    //要求已经存在
    int sourceFile;
    //要求不能存在
    int targetFile;
    sourceFile=open(argv[1],O_RDONLY);
    if(sourceFile==-1)
    {
        printf("源文件不存在。\n");
        exit(1);
    }
    targetFile=open(argv[2],O_WRONLY|O_CREAT|O_EXCL,0644);
    if(targetFile==-1)
    {
        printf("目标文件以存在。\n");
        exit(1);
    }
    //将缓冲区大小定为101个字节
    char buf[100];
    //记录读到文件的真实大小
    int len;
    while(len=(read(sourceFile,buf,100)))
    {
        write(targetFile,buf,len);
    }
    close(sourceFile);
    close(targetFile);
    printf("文件以拷贝完成,程序退出。\n");
    return 0;
}

打开文件一定要记得关闭,虽然在一般在程序结束时操作系统会自动关闭,但这是一个非常不好的习惯。

这里需要强调一点一个程序默认最大打开1024个文件(可以修改)。我们通过ulimit -a命令可以查看。

可以通过ulimit -n来修改。

但是这个也是有上限的。可以通过cat /proc/sys/fs/file-max可以查看(和内存大小有关)。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值