基础IO(总)

接口介绍

open:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname:要打开或创建的目标文件

flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行"或"运算( '|' )

参数:

  • O_RDONLY:只读打开
  • O_WRONLY:只写打开
  • O_RDWR:读,写打开。这三个参数必定指定一个且只能指定一个
  • O_CREAT:若文件不存在,则创建它,需要使用mode选项,来指名新文件的访问权限
  • O_APPEND:追加写

返回值:成功就返回新打开的文件描述符,失败就返回-1

使用:open函数具体使用哪个分场景而定,如果目标文件不存在,需要open创建,就要使用三个参数,并且设置新创建文件的权限。

int open(pathname,O_CREAT,0666);//文件不存在,创建它并设置权限为0666

OPEN函数的返回值 

在学习返回值之前,先了解一下系统调用和库函数。

  • 像fopen,fclose,fread,fwrite这些都是C标准库中的函数 ,称之为库函数
  • 而open,close,read,write,lseek,这些都是OS提供的系统调用

 

 所以可以认为f#系列的函数,都是队系统调用函数进行了封装,方便进行二次开发

 


 

 文件描述符fd

通过上面的open函数就可以知道,文件描述符fd就是一个小整数

0&1&2:

在Linux下,默认会有三个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2

0对应的是键盘,1和2对应的是显示器,即0是从键盘读入,1和2是像显示器输出。

 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
    char buf[1024];
    ssize_t s = read(0, buf, sizeof(buf));
    if(s > 0){
        buf[s] = 0;
        write(1, buf, strlen(buf));
        write(2, buf, strlen(buf));
    }
    return 0;
}

上面代码就是利用系统调用接口write像显示器输出buf中的内容,用read从键盘上读取内容保存到数组buf中. 

 

 总结:文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中创建相应的数据结构来描述文件,于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files,指向一张表files_struct,该表最重要的部分就是包含一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该指针数组的下标。所以只要知道文件描述符,就可以找到对应的文件.

 

文件描述符的分配规则 

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    close(fd);
    return 0;
}

 最后输出的答案是" fd: 3"

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
    close(0);
    //close(2);
    int fd = open("myfile", O_RDONLY);
    if(fd < 0){
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    close(fd);
    return 0;
}

输出的是"fd: 0"。

可以得出结论:文件描述符的分配规则:在file_struct数组中,找到当前没有被使用的最小的下标,作为新的文件描述符


重定向

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
    close(1);
    int fd = open("myfile", O_WRONLY|O_CREAT, 0644);
    if(fd < 0){
        perror("open");
        return 1;
    }
    printf("fd: %d\n", fd);
    fflush(stdout);
    close(fd);
    exit(0);
}

 这时候我们发现本来应该输出到显示器上的内容,输出到了文件myfile中,其中fd = 1.这种输出现象叫做重定向。常见的重定向有>,>>,<

一张图了解重定向本质:

 


 dup2系统调用

函数原型:

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

dup系统调用分配的文件描述符是由系统分配的,遵循文件描述符的分配原则,并不能指定一个文件描述符,这是dup的一个缺陷,而dup2就很好的解决了这个问题

oldfd:需要被复制的文件描述符

newfd:指定一个文件描述符(需要指定一个当前进程没有使用到的文件描述符)

返回值:成功时返回一个新的文件描述符,也就是newfd;失败就返回-1 

示例:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
    int fd = open("./log", O_CREAT | O_RDWR);
    if (fd < 0) {
        perror("open");
        return 1;
    }
    close(1);
    dup2(fd, 1);
    for (;;) {
        char buf[1024] = {0};
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);
        if (read_size < 0) {
            perror("read");
            break;
        }
        printf("%s", buf);
        fflush(stdout);
    }
    return 0;
}

 跟上面的代码一个效果。刚开始创建文件的时候,根据文件描述符分配原则,获得的fd应该为3,

这个时候调用dup2(fd,1)将fd的文件描述符改成1,并断开原先的标准输出,如上述重定向图一样。


FILE 

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。

借由下面一段代码来理解:

#include <stdio.h>
#include <string.h>
int main()
{
    const char *msg0="hello printf\n";
    const char *msg1="hello fwrite\n";
    const char *msg2="hello write\n";
    printf("%s", msg0);
    fwrite(msg1, strlen(msg0), 1, stdout);
    write(1, msg2, strlen(msg2));

    fork();
    return 0;
}

运行结果:

hello printf
hello fwrite
hello write

但是如果对文件进行输出重定向的话。./test > file 则文件file中会有

hello write
hello printf
hello fwrite
hello printf
hello fwrite

可以发现printf和fwrite都输出了两次,这两个都是库函数,而write只输出了一次,write为系统调用

  •  一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲的
  • printf fwrite库函数会自带缓冲区,当发生重定向到普通文件时候,数据的缓冲方式由行缓冲变成了全缓冲
  • 而我们放在缓冲区中的数据,不会被立即刷新,甚至fork之后
  • 但是进程退出之后,会统一刷新,写入文件当中
  • 但是fork时候,父子数据会发生写时拷贝,所以当父进程准备刷新的时候,子进程就有了同样的一份数据,随机就会产生两份同样的数据
  • write没有变化说明,write没有所谓的缓冲

综上:printf fwite库函数会自带缓冲区,而write系统调用没有带缓冲区,另外,我们这里的缓冲区都是用户级的缓冲区,作用就是提升整机性能,OS也会提供相关内核级缓冲区。

那这个用户级缓冲区由谁提供?

显然是C标准库提供,上述中printf和fwrite都是库函数,write是系统调用,库函数在系统调用的上层,是对系统调用的封装,但是write没有缓冲区,而printf和fwrite有缓冲区,说明缓冲区是在封装的时候加上的,也就是由C标准库提供的。 


理解文件系统 

在linux下使用指令ls -l

 [root@localhost linux]# ls -l
-rwxr-xr-x. 1 root root 7438 "1月 1 14:56" a.out
-rw-r--r--. 1 root root 654 "1月 1 14:56" test.c

每行包含7列:

  •  模式
  • 硬连接数
  • 文件所有者
  • 大小
  • 最后修改时间
  • 文件名

inode:

为了解释清楚inode,先简单了解一下文件系统

磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,不可以更改。下图中的启动快(Boot Block)的大小是可以确定的

 

  • Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。每个BlockGroup都有相同的结构组成。
  • 超级块(Super Block):存放文件系统本身的结构信息,记录的信息主要有:block和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block中哪个数据块已经被占用,哪个数据块没有被占用
  • 块位图(Block BItmap):Block Bitmap中记录者Data Block中哪个数据块已经被占用,哪个数据块没有被占用
  • inode位图(inode Bitmap):每个bit表示一个inode是否可空闲使用
  • i节点表:存放文件属性 如,文件大小,所有者,最近修改时间
  • 数据区:存放文件内容

将属性和数据分开存放的想法看起来很简单,看下图。

[root@localhost linux]# touch abc
[root@localhost linux]# ls -i abc
261234 abc

 

 创建一个新文件主要有以下四个操作

  1. 存储属性内核先找到一个空闲的i节点(这里是261234)。内核把文件信息记录到其中
  2. 存储数据该文件需要存储在三个磁盘块,内核找到了三个空闲块:300,500,800。将内核缓冲区的第一块数据复制到300,下一块复制到500,以此类推.
  3. 记录分配情况文件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。
  4. 添加文件名到目录

新的文件名字abc。内核将入口(261234,abc)添加到目录文件。文件名和inode之间的对应关系将文件名和文件的内容及属性连接起来(建立映射关系)。


软硬链接  

硬链接 :

 特点:

  • 具有相同inode节点号的多个文件互为硬链接 文件
  • 删除硬链接 文件或者删除源文件任意之一,文件实体并未被删除
  • 只有删除了源文件和所有对应的硬连接文件,文件实体才会被删除
  • 硬链接 ​​​​​​文件是文件的另一个入口
  • 可以用过给文件设置硬连接文件来防止重要文件被误删
  • 可以通过ls -i看到index;
  • 硬链接文件是普通文件,可以用rm删除
  • 对于静态文件(没有进程正在调用),当硬链接数为0时文件就被删除。注意:如果有进程正在调用,则无法删除或者及时文件名被删除空间也不会释放

我们看到,真正找到磁盘上文件的并不是文件名,而是inode。 其实在linux中可以让多个文件名对应于同一个inode
 [root@localhost linux]# touch abc

[root@localhost linux]# ln abc def

[root@localhost linux]# ls -1i
abc def 261234 abc 261234 def

  1. abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个链接数,inode261234 的硬连接数为2
  2. 我们在删除文件时干了两件事情:1.在目录中将对应的记录删除,2.将硬链接数-1,如果为0,则将对应的磁盘释放

 

软链接 :

特点:

  • 软链接 类似windows系统的快捷方式
  • 软链接里面存放点的是源文件的路径
  • 删除源文件,软链接依然存在,但无法访问源文件内容
  • 软链接失效时一般是白字红底闪烁
  • 创建软链接命令ln -s 源文件 软链接文件;
  • 软链接文件和源文件是不同的文件,稳健类型也不同,inode号也不同
  • 软链接的文件类型是"I",可以用rm删除

硬链接是通过inode引用另外一个文件,软链接是通过名字引用另外一个文件,在shell中的做法

263563 -rw-r--r--. 2 root root 0 9月 15 17:45 abc
261678 lrwxrwxrwx. 1 root root 3 9月 15 17:53 abc.s -> abc
263563 -rw-r--r--. 2 root root 0 9月 15 17:45 def

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Obto-

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值