基础I/O

一、系统I/O

(一)与文件相关的系统调用:

1、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:打开文件时,可以传入以下选项和选项的异或运算,构成flags

                O_RDONLY:只读方式打开

                O_WRONLY:只写方式打开

                O_RDWR:读写方式打开

                以上三种方式,必须包含一个且只能包含一个。

                O_APPEND:追加写

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

  • mode:当新文件创建时,指明新文件的访问权限。当flags有O_CREAT选项时,必须指定权限。

返回值:打开成功返回打开文件的文件描述符;打开失败则返回-1.

2、close:
#include <unistd.h>
int close(int fd);

参数:fd:文件描述符

返回值:成功返回0,;失败返回-1

3、read:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);

含义:从fd中将count个字节的数据到buf中

返回值:若成功,返回读取的字节数;若失败,返回-1.

4、write:
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);

含义:将buf中的数据的count个字节的数据写入fd的文件中。

返回值:若成功,返回写入的字节数;若失败,返回-1.


eg:从标准输入读入数据并写到文件中。

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

int main()
{
    int fd = open("test", O_RDWR | O_CREAT, 0644);
    if(fd < 0)                                                                                                                       
    {   
        perror("open");
        return -1; 
    }   
    char buf[1024] = {0};
    ssize_t rret = read(0, buf, 12);
    if(rret < 0)
    {
        perror("read");                                                                                                              
        return -1;
    }
    ssize_t wret = write(fd, buf, 12);
    if(wret < 0)
    {
        perror("write");
        return -1;
    }
    close(fd);
    return 0;
}

(二)文件描述符

上面介绍系统IO时,提到了文件描述符。那么文件描述符究竟是什么呢?

我们知道fopen等C语言的库函数是open等系统调用的一层封装。

通过open等函数,我们已经了解到文件描述符是一个整数,那么这些整数有什么含义呢?

我们写一个函数实现打开一个文件,打印出它的文件描述符:

int main()
{
    int fd = open("test", O_RDWR | O_CREAT, 0644);
    if(fd < 0)
    {   
        perror("open");
        return -1;
    }
    printf("fd:%d\n", fd);
    close(fd);
    return 0;
}                                                                                                                                    

发现打印出来的是3,那0,1和2呢?

答:Linux进程默认情况下会有三个缺省打开的文件描述符,0,1和2分别是标准输入,标准输出,和标准错误。它们对应的硬件设备分别是键盘,显示器,显示器。

int main()
{
    char buf[1024] = {0};
    //从标准输入读取数据
    ssize_t ret = read(0, buf, sizeof(buf));
    if(ret < 0)
    {
        perror("read");
        return -1;
    }
    buf[ret] = '\0';
    //把buf中的数据写进标准输出
    ssize_t rret = write(1, buf, strlen(buf));
    if(rret < 0)                                                                                                                     
    {
        perror("write into stdout");
        return -1;
    }
    //把buf中的数据写进标准输出                                                                                                      
    ssize_t rrret = write(2, buf, strlen(buf));
    if(rrret < 0)
    {
        perror("write into stderr");
        return -1;
    }
    return 0;
}

从结果也可以看出来,fd为0,1和2分别代表的含义:



那么文件描述符究竟是如何找到要打开的文件,它的原理是什么呢?


答:我们在进程中要打开文件,就要把进程和文件联系起来。进程的PCB中就保存有这样一个指针,指向一张表files_struct,该表保存的是该进程打开的文件指针,每一个文件指针又执行一个个结构体,结构体中保存着打开文件的inode元信息。通过以上,一步一步可以找到文件。


注意:

文件描述符的分配是从0开始,找到当前未使用的最小下标的位置,作为新的文件描述符。

eg:如果我们关闭默认的标准输出,那么新创建的文件描述符就会从1开始

int main()
{         
    close(1);
    char buf[1024] = "hello bit";
    int fd = open("./mytest", O_WRONLY | O_CREAT, 0644);
    if(fd < 0)
    {     
        perror("open");
        return -1;
    }     
    printf("fd:%d\n", fd);
    fflush(stdout);                                                                                                                  
    int ret = write(fd, buf, strlen(buf));
    if(ret < 0)
    {
        perror("write");
        return -1;                                                                                                                   
    }

    close(fd);
    return 0;
}


(三)重定向

上面关闭标准输入,新打开的文件描述符就会是1,就是一个重定向的过程。把本来应该写入标准输出的内容写到了mytest文件中。怎么理解重定向呢?我们通过一张图来理解:



(四)缓冲

观察下面的函数运行结果:

int main()       
{                
    printf("hello printf\n");
    char buf1[1024] = "hello fwrite\n";
    fwrite(buf1, sizeof(char), strlen(buf1), stdout);
    char buf2[1024] = "hello write\n";
    write(1, buf2, strlen(buf2));                                                                                                    
    fork();              
    return 0;
}  

把内容从写道标准输出该为写到指定文件,或者进行重定向到文件,观察结果:


从上面的运行结果中我们发向同样的代码,运行出来结果不同,重定向后hello printf和hello fwrite都多打印了一次,这是为什么呢?

答:write是系统调用,无缓冲区;而printf和fwrite都是C语言函数,当写入显示器时,是以行缓冲方式工作,遇到‘\n’时刷新缓冲区,将结果写到指定文件。当重定向到普通文件时,变为全缓冲方式工作,进程退出时,才会刷新缓冲区。

所以,write无缓冲区,只写入一次;而printf和fwrite从行缓冲变为全缓冲,fork又拷贝了父进程的代码和数据,还包括缓冲区中的内容,父子进程退出时,都刷新一次缓冲区,就造成了上面写入2次的情况。

二、理解文件系统

(一)文件系统

我们可以用ls -l, ll或stat来查看文件信息:



用stat查看文件更多详细信息时,发现有两个属性必须要先理解文件系统才能理解。

让我们先简单理解下文件系统:


        创建一个新文件,内核先找到一个空闲的节点,把文件信息记录到其中;如图中,假设创建的文件需要存储在三个内存块,内核找到了三个空闲快300、500和800,按顺序将内核缓冲区的第一块数据复制到300,下一块复制到500,依次进行;i节点会保存使用到的内存块编号;


        那么Linux如何在当前目录中记录这个文件呢?内核将入口(17711350 abc)添加到目录文件;inode和文件名之间的对应关系将文件和文件内容和属性联系在一起。

(二)硬连接

硬链接就是使多个文件名映射到同一个inode。


观察硬链接数变为了2:


当删除其中一个文件时,会删除要删除的文件,并将硬链接数-1:


(三)软链接

硬链接是通过inode引用另一个文件;而软连接是通过名字引用另外一个文件。


三、动态库和静态库

(一)静态库

程序在编译连接的时候将库的代码链接到可执行文件中,运行时就不再需要静态库了。

//add.h
#pragma once
int add(int a, int b); 

//add.c
#include "add.h"                                                                                                                     
int add(int a, int b)
{
    return a+b;
}

//main.c
#include "add.h"

int main()
{
    int a = 10; 
    int b = 29; 
    int ret = add(a, b); 
    printf("%d+%d=%d\n", a, b, ret);                                                                                                 
    return 0;
}                                                                                                       
main:main.c libadd.a
    gcc $^ -o $@
libadd.a:add.o                                                                                                                       
    ar -rc libadd.a add.o    //生成静态库
add.o:add.c                   
    gcc -c $^ -o $@           
.PHONY:clean                  
clean:                        
    rm add.o libadd.a main    


(二)动态库

程序在运行的时候才会去链接动态库中的程序,多个程序共享使用库的代码。

在可执行文件开始执行之前,外部函数的机器码由操作系统从磁盘上的该动态库中复制到内存中,这个过程叫做动态链接


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值