7.顺序与随机读写、系统IO与标准IO、文件描述符的复制、文件控制

顺序与随机读写

1.每个打开的文件都有一个与其相关的文件读写位置保存在文件表项中,用以记录从文件头开始计算的字节偏移量,文件读写位置通常是一个非负的整数,用off_t类型表示,在32位系统上被定义为long int,而在64位系统上则被定义为long long int

2.打开一个文件时,除非制定了O_APPEND(追加)标志,自动把读写位置偏移到文件尾,否则文件读写位置一律被设为0,即文件首字节的位置

3.每一次读写操作都从当前的文件读写位置开始,并根据所读写的字节数,同步增加文件读写位置,为下一次读写做好准备

4.因为文件读写位置是保存在文件表项而不是v节点中的,因此通过多次打开同一个文件得到多个文件描述符,各自拥有各自的文件读写位置

#include

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

        功能:人为调整文件读写位置

        参数:fd:文件描述符

                offset:文件读写位置偏移字节数

                whence:offset参数的偏移起点,可以如下取值

                        SEEK_SET -从文件头(首字节)开始

                        SEEK_CUR - 从当前位置(最后被读写字节的下一个字节)开始

                        SEEK_END - 从文件尾(最后一个字节的下一个字节)开始

        返回值:成功返回调整后的读写位置,失败返回-1

5.lseek函数的功能仅仅是修改保存在文件表项中的文件读写位置,并不实际引发任何I/O动作

        lseek(fd,-7,SEEK_CUR); //从当前位置向文件头偏移7字节

        lseek(fd,0,SEEK_CUR); //返回当前文件读写位置

        lseek(fd,0,SEEK_END); //返回文件总字节数,不是字节个数,比如,int a[10];返回的是字节个数不是元素个数,字节数是8,元素是2,写入位置其实是不记录\0的,比如write(fd,"hello world!",15)写入后,其读写位置在12而不是13,可用off_t pos = lseek(fd,0,SEEK_CUR);来看。

 

 

 6.可以通过lseek函数将文件读写位置设到文件尾之后,在超过文件尾的位置上写入数据,将在文件中形成空洞位于文件中但没有被写过的字节都被设置为0,数字0对应的字符是\0 文件空洞不占磁盘空间,但被计算在文件大小之内

//文件读写位置调整
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>

int main(void){
    //打开一个文件 此时文件读写位置为0 
    int fd = open("./lseek.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    //向文件中写入  hello world!   此时文件读写位置为 12
    char* buf = "hello world!";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    off_t pos = lseek(fd,0,SEEK_CUR);
    if(pos == -1){
        perror("lseek");
        return -1;
    }
    printf("%ld\n",pos);
    //调整文件读写位置,到空格之后,会覆盖掉world!
    if(lseek(fd,-6,SEEK_END) == -1){
        perror("lseek");
        return -1;
    }
    //再次向文件写入数据  linux!
    buf = "linux!"; 
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }

    //再再次调整读写位置
    if(lseek(fd,8,SEEK_END) == -1){
        perror("lseek");
        return -1;
    }

    //再再次写入数据
    buf = "<--this is a hole";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    close(fd);
    return 0;
}

 

 

 

 

系统I/O与标准I/O

1,当系统调用函数被执行时,需要在用户态和内核态之间来回切换,因此频繁执行系统调用函数会严重影响性能

2.标准库做了必要的优化,内部维护一个缓冲区,只在满足特定条件时才将缓冲区与系统内核同步,借此降低执行系统调用的频率,减少进程在用户态和内核态之间来回切换的次数,提高运行性能

标准库函数:fopen open

                        fread read

                        fwrite write

                        fclose close

相同的内容 重复1000次写入,是用标准库快还是系统调用的快

vim stdio.c

//标准库
#include<stdio.h>

int main(void){
    FILE* fp = fopen("./std.txt","w"); //返回的是文件指针
    if(fp == NULL){
        perror("fopen");
        return -1;
    }

    for(int i = 0;i < 100000;i++){
        fwrite(&i,sizeof(i),1,fp);
    } //i地址,i大小,一次操作多大的存储区,1,往fp写

    fclose(fp);
    return 0;
}

vim sysio.h 

//系统调用
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>

int main(void){
    int fd = open("./sys.txt",O_WRONLY | O_CREAT | O_TRUNC,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }

    for(int i = 0;i < 100000;i++){
        write(fd,&i,sizeof(i));
    }
    
    close(fd);
    return 0;
}

time ./stdio

 time ./sysio

不是调用一次fwrite就要去调用底层的write,而是先写入一个临时缓冲区,写满或者写完了才会掉一次write,

文件描述符的复制

#include

int dup(int oldfd);

        功能:复制文件描述符表的特定条目到最小可用项

        参数:oldfd:源文件描述符

        返回值:成功返回目标文件描述符,失败返回-1

1.dup函数将oldfd参数所对应的文件描述符表项复制到文件描述符第一个空闲项中,同时返回该参数对应的文件描述符。dup函数返回的文件描述符一定是调用进程当前未使用的最小文件描述符

2.dup函数只复制文件描述符表项,不复制文件表项和v节点,因此该函数所返回的文件描述符可以看做是参数文件描述符oldfd的副本,它们标识同一个文件表项

3.注意,当关闭文件时,即使是由dup函数产生的文件描述符副本,也应该通过close函数关闭,因为只有当关联与一个文件表项的所有文件描述符都被关闭了,该文件表项才会被摧毁,所有关联与一个v节点的所有文件表项都被销毁了,v节点才会被从内存中删除,因此从资源合理利用的角度讲,凡是明确不再继续使用的文件描述符,都应该尽可能及时地使用close函数关闭

4.dup函数究竟会把oldfd参数所对应地文件描述符表项,复制到文件描述符地什么位置,程序员是无法控制的,这完全由调用该函数时文件描述符表的使用情况决定,因此对该函数的返回值做任何约束性假设都是不严谨的

5.由dup函数返回的文件描述符与作为参数传递给该函数的文件描述符标识的是同一个文件表项,而文件读写位置是保存在文件表项中的,因此通过这些文件描述符中的任何一个,对文件进行读写或随机访问,都会影响通过其它文件描述符操作的文件读写位置。这与多次通过open函数打开同一个文件不同。

//文件描述符的复制
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>

int main(void){
    int fd = open("./dup.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    printf("fd == %d\n",fd);

    //文件描述符的复制
    int newfd = dup(fd);
    if(newfd == -1){
        perror("dup");
        return -1;
    }
    printf("newfd == %d\n",newfd);

    //通过fd 向文件写入hello world!
    char* buf = "hello world!";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    //通多newfd修改文件读写位置
    if(lseek(newfd,-6,SEEK_END) == -1){
        perror("lseek");
        return -1;
    }
    
    //再次通过fd写入linux!
    buf = "linux!";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }

    close(fd);
    close(newfd);
    return 0;
}

 

#include

int dup2(int oldfd,int newfd);

        功能:复制文件描述符表的特定条目到指定项

        参数:oldfd:源文件描述符

                    newfd:目标文件描述符

        返回值:成功返回目标文件描述符(newfd),失败返回-1.

        dup2函数在复制由oldfd参数所标识的源文件描述符表项时,会先检查由newfd参数所标识的目标文件描述符表项是否空闲,若空闲则直接将前者复制给后者,否则会先将目标文件描述符newfd关闭,使之成为空闲项,再行复制

//文件描述符的复制
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>

int main(void){
    int fd = open("./dup.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    printf("fd == %d\n",fd);

    //文件描述符的复制
    //int newfd = dup(fd);
    int newfd = dup2(fd,1);
    //int newfd = fcntl(fd,F_DUPFD,1);
    if(newfd == -1){
        perror("dup");
        return -1;
    }
    printf("newfd == %d\n",newfd);

    //通过fd 向文件写入hello world!
    char* buf = "hello world!";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    //通多newfd修改文件读写位置
    if(lseek(newfd,-6,SEEK_END) == -1){
        perror("lseek");
        return -1;
    }
    
    //再次通过fd写入linux!
    buf = "linux!";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }

    close(fd);
    close(newfd);
    return 0;
}

使用dup2后,由于文件描述符1是非常特殊的,输出,因此此时调用printf,是写入到dup.txt的,因此上文中的printf(newfd == 1);在dup.txt中,如果同时还想要printf,那么就把原来的1复制到5上,然后write

 

其实这也是输出重定向的一种方式,如果使用文件描述0作为newfd,那么键盘输入就是把输入字符串写入文件中了。

注意!!!!!!!!!!!!!!!!!!!!!!!

这里如果你修改一下代码,把第一条printf注释掉,你会发现,我的printf("newfd == ")这一条语句没有出现在txt中,这是因为输出缓冲区的问题,printf会先放放入缓冲区中,但是程序执行到close后,会自动关闭缓冲区清楚其中数据,因此,在开头加上setbuf(stdout,NULL)或者干脆close(1);都有关闭缓冲区的效果,此时再度运行则txt中又出现 newfd == 1

 

文件控制

fcnt:垃圾箱函数,有很多边角功能,

#include

int fcnt(int oldfd,F_DUPFD,int newfd);

        功能:复制文件描述符

        参数:oldfd:源文件描述符

                    newfd:目标文件描述符

        返回值:成功返回目标文件描述符(可能是newfd),失败返回-1

        fcntl函数在复制由oldfd参数所标识的源文件描述符表项时,会先检查由newfd参数所标识的目标文件描述符表项是否空闲,所空闲则直接将前者复制给后者,否则并不会将其关闭,而是另外找一个空闲的文件描述符作为复制目标。

        cmd接不同的宏,有不同功能; ... 是不定长参数

//文件描述符的复制
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>

int main(void){
    int fd = open("./dup.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
    if(fd == -1){
        perror("open");
        return -1;
    }
    printf("fd == %d\n",fd);

    //文件描述符的复制
    //int newfd = dup(fd);
    //int newfd = dup2(fd,1);
    int newfd = fcntl(fd,F_DUPFD,1);
    if(newfd == -1){
        perror("dup");
        return -1;
    }
    printf("newfd == %d\n",newfd);

    //通过fd 向文件写入hello world!
    char* buf = "hello world!";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }
    //通多newfd修改文件读写位置
    if(lseek(newfd,-6,SEEK_END) == -1){
        perror("lseek");
        return -1;
    }
    
    //再次通过fd写入linux!
    buf = "linux!";
    if(write(fd,buf,strlen(buf)) == -1){
        perror("write");
        return -1;
    }

    close(fd);
    close(newfd);
    return 0;
}

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一般要将硬盘分成主引导扇区、操作系统引导扇区、FAT表、DIR目录区和Data数据区等五部分,这里分别进行了介绍!硬盘的文件系统结构 • 新买的硬盘,是没有直接办法使用的,需要将它分区、格式化,然后再安装上操作系统才可以使用。就拿一直沿用到现在的Win9x/Me/2000/XP系列来说,一般要将硬盘分成主引导扇区、操作系统引导扇区、FAT表、DIR目录区和Data数据区等五部分。通常所说的主引导扇区MBR在一个硬盘中是是唯一的,MBR区的内容只有在硬盘启动时才读取其内容,然后驻留内存。其它几项内容随你的硬盘分区数的多少而异。 • 主引导扇区(MBR)。主引导扇区位于整个硬盘的0磁道0柱面1扇区,包括硬盘主引导记录MBR(Main Boot Record)和分区表DPT(Disk Partition Table)。其中主引导记录的作用就是检查分区表是否正确以及判别哪个分区为可引导分区,并在程序结束时把该分区的启动程序(也就是操作系统引导扇区)调入内存加以执行。 • 分区表(DPT)。在主引导区中,从地址BE开始,到FD结束为止的64个字节中的内容就是通常所说的分区表。分区表以80H或00H为开始标志,以55AAH为结束标志,每个分区占用16个字节,一个硬盘最多只能分成四个主分区,其中扩展分区也是一个主分区。随着硬盘容量的迅速扩大,引入的扩展分区可以不受四个主分区的限制,把硬盘分区数扩展到“Z”。值得一提的是,MBR是由分区程序(例如DOS的Fdisk.exe)产生的,不同的操作系统可能这个扇区的内容代码是不相同,但是实现的功能只有一个,使其中的一个活动分区获得控制区,正常启动系统。 • 主引导扇区(MBR)。主引导扇区位于整个硬盘的0磁道0柱面1扇区,包括硬盘主引导记录MBR(Main Boot Record)和分区表DPT(Disk Partition Table)。其中主引导记录的作用就是检查分区表是否正确以及判别哪个分区为可引导分区,并在程序结束时把该分区的启动程序(也就是操作系统引导扇区)调入内存加以执行。 • 主分区和扩展分区。主分区是一个比较单纯的分区,通常位于硬盘的最前面一区域中,构成逻辑C磁盘。在主分区中,不允许再建立其它逻辑磁盘。也可以通过分区软件,在分区的最后建立主分区,或在磁盘的中部建立主分区。扩展分区的概念则比较复杂,也是造成分区和逻辑磁盘混淆的主要原因。由于硬盘仅仅为分区表保留了64个字节的存储空间,而每个分区的参数占据16个字节,故主引导扇区中总计可以存储4个分区的数据。操作系统只允许存储4个分区的数据,如果说逻辑磁盘就是分区,则系统最多只允许4个逻辑磁盘。对于具体的应用,4个逻辑磁盘往往不能满足实际需求。为了建立更多的逻辑磁盘供操作系统使用,系统引入了扩展分区的概念。 所谓扩展分区,严格地讲它不是一个实际意义的分区,它仅仅是一个指向下一个分区的指针,这种指针结构将形成一个单向链表。这样在主引导扇区中除了主分区外,仅需要存储一个被称为扩展分区的分区数据,通过这个扩展分区的数据可以找到下一个分区(实际上也就是下一个逻辑磁盘)的起始位置,以此起始位置类推可以找到所有的分区。无论系统中建立多少个逻辑磁盘,在主引导扇区中通过一个扩展分区的参数就可以逐个找到每一个逻辑磁盘。 • 操作系统引导扇区(OBR)。OBR(OS Boot Record)即操作系统引导扇区,通常位于硬盘的0磁道1柱面1扇区(这是对于DOS来说的,对于那些以多重引导方式启动的系统则位于相应的主分区/扩展分区的第一个扇区),是操作系统可直接访问的第一个扇区,它也包括一个引导程序和一个被称为BPB(BIOS Parameter Block)的本分区参数记录表。其实每个逻辑分区都有一个OBR,其参数视分区的大小、操作系统的类别而有所不同。引导程序的主要任务在当根目录中寻找系统文件IO.SYS,MSDOS.SYS和WINBOOT.SYS三个文件,如果存在,就把IO.SYS文件读入内存,并移交控制权予该文件。在WIN98的系统中,没有MSDOS.sys文件系统能够正常启动,但是无法进入桌面;如果没有COMMAND.COM文件,能够正常启动到桌面,但是无法进入DOS字符方式。 BPB参数:记录着本分区的起始扇区、结束扇区、文件存储格式、硬盘介质描述符、根目录大小、FAT个数、分配单元(Allocation Unit,以前也称之为簇)的大小等重要参数。OBR由高级格式化程序产生(例如DOS 的Format.com)。 • 文件分配表(FAT)。FAT(File Allocation Table)即文件分配表,是DOS/Win9x系统文件寻址系统。为了防止意外损坏,FAT一般做两个(也可以设置为一个),第二FAT为第一FAT的备份, FAT区紧接在OBR之后(对于FAT32格式,位置是从引导扇区开始的第32个扇区就是第一个FAT表的位置),其大小由这个分区的空间大小及文件分配单元的大小决定。随着硬盘容量的迅速发展,Microsoft 的DOS及Windows也先后采用我们所熟悉的FAT12、FAT16和FAT32格式。不过Windows NT、OS/2、UNIX/Linux、Novell等都有自己的文件管理方式,不同于FAT文件格式。FAT12是使用12BIT来表示簇的位置,最大容量32M,FAT16是使用两个字节16BIT位来表示簇的位置,分区最大容量2G,而FAT32采用4个字节来表示簇的位置,分区最大容量65G。 • 目录区(DIR)。DIR是Directory即根目录区的简写,在FAT12和FAT16格式中,DIR紧接在第二FAT表之后,而在FAT32格式中,根目录区的位置可以在分区中的任意位置,其起始位置是由引导扇区给出的。单有FAT表还不能确定文件在磁盘中的具体位置,只有FAT表和DIR区配合使用,才能准确定位文件的确切位置。DIR记录着每个文件(目录)的文件名,扩展名,是否支持长文件各,起始单元(这是最重要的)、文件的属性,大小,创建日期,修改日期等住处内容。操作系统读写文件时,根据DIR中的起始单元,结合FAT表就可以知道文件在磁盘的具体位置,然后顺序读取每个簇的内容就可以了。 • 数据区(DATA) 。在DIR区之后,才是真正意义上的数据存储区,即DATA区。DATA虽然占据了硬盘的绝大部分空间,但没有了前面的各部分,它对于我们来说,也只能是一些枯燥的二进制代码,没有任何意义。注意:我们通常所说的格式化程序(指高级格式化,例如DOS下的Format程序),并没有把DATA区的数据清除,只是重写了FAT表而已,除非你使用了“Format X: /U”命令,强制对每一扇区写“F6”。 至于硬盘分区,也只是修改了MBR和OBR,绝大部分的DATA区的数据并没有被改变,这也是许多硬盘数据能够得以修复的原因。但即便如此,MBR,OBR,FAT,DIR之一被破坏的话,我们的数据也无法正常读取。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值