欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

程序员文章站 2022-05-24 20:14:52
...

 

一.练习open/read/write/close等文件相关系统调用接口,纵向对比fd和FILE结构体

1. open函数

 

 

(1)函数原型

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

(2)函数功能

 

        用于打开或创建文件,在打开或创建文件可以指定文件的属性及用户的权限等。若目标文件不存在,需要创建文件时,使用三个参数的open函数,否则,使用两个参数的open。

(3)参数

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

        2)flags : 打开文件时,可传入多个参数选项,用以下的一个或多个常量进行“”运算,构成flags:

                    O_RDONLY :只读打开;

                    O_WRONLY :只写打开;

                    O_RDWR :读写打开; (这三个常量,必须指定一个

                    O_CREAT :若文件不存在,则创建它;(需要mode选项,来指明新文件的访问权限)

                    O_APPEND :追加写。

        3)mode :用户的访问权限:可用R/W/X表示,也可以用八进制表示

(4)返回值:

        成功时返回打开的文件的文件描述符;失败返回-1。

 

 

 

2. read 函数

(1)函数原型

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

(2)函数功能

        从文件里读数据。

(3)参数

 

        1)fd :要读取数据的文件的文件描述符;

        2)buf :指缓冲区,要 有一个缓冲区接收读取的数据;

        3)count :表示调用一次read,应该读取多少数量的字符。

 

 

(4)返回值:

        返回读取到的字符数:0表示读到EOF,-1表示出错。

3. write 函数

(1)函数原型

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

(2)函数功能

        向目标文件里写内容

(3)参数

 

 

        1)fd :要写入的文件的文件描述符;

        2)buf :要向文件里写入的内容;

        3)count :写入多少内容。

(4)返回值

 

 

 

        成功则返回写入文件的字符数;失败返回-1。

4. close 函数

 

 

 

(1)函数原型

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

(2)函数功能

        关闭已打开的文件。

(3)函数参数

         fd :要关闭的文件的文件描述符。

(4)返回值

        0成功;-1出错。

5. 利用以上函数实现写文件

编写代码如下:

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

int main()
{
    umask(0);//关闭读
    int fd = open("myfile", O_WRONLY|O_CREAT,0644);
    if(fd < 0)//打开失败
    {   
        perror("open");
        exit(1);
    }   
    int count = 5;
    const char* msg = "hello may!\n";
    while(count--)
    {   
        write(fd, msg ,strlen(msg));//向文件里写字符串不写入字符串的结束标志'\0'
    }   
    close(fd);
    return 0;
}

运行结果如下:

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

其中,myfile文件是该程序创建出来的文件,五条“hello may!"也是该程序写入的数据。

 

 

 

6. 利用以上函数实现从文件里读数据

编写代码如下:

 

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

int main()
{
    umask(1);//关闭写
    int fd = open("myfile", O_RDONLY);
    if(fd < 0)//打开失败
    {   
        perror("open");
        exit(1);
    }   
    const char* msg = "hello may!\n";
    char buf[1024];
    while(1)
    {   
        size_t ret = read(fd, buf ,strlen(msg));//类似write
        if(ret < 0)//read失败
        {   
            perror("read");
            break;
        }   
        else if(ret == 0)//文件结束
        {   
            break;
        }   
        else
        {   
            buf[ret] = '\0';//读完文件内容,给其加上结束标志
            printf("%s\n",buf);
        }   
    }   
    close(fd);
    return 0;
}

 

运行结果如下:

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

 

 

8. 纵向比对 fd 与 FILE 结构体

(1)文件描述符 fd

        通过以上代码,我们可以知道:文件描述符(file descriptor)就是一个从0开始的小整数。

        通常情况下,将一个程序从硬盘加载到内存后,这个程序就变成了一个进程。Linux进程默认情况下会有3个缺省打开的文件描述符:0标准输入(stdin)、1标准输出(stdout)、2标准错误(stderr),分别对应的物理设备一般是:键盘、显示器、显示器。这三个文件相对应的三个文件描述符分别为0、1、2。所以后面创建新文件时,新文件的文件描述符不可能是0、1、2。所以在Linux中,文件描述符的分配是从3开始,从当前最小的且未被分配的文件描述符中分配。

        当我们打开文件时,OS要在内存中创建相应的数据结构来描述目标文件。所以有了一个file结构体,用来表示已打开的文件对象。而进程要执行open等操作,所以需要让进程与文件关联起来。所以每个进程都有一个指针*files,指向一张表files_struct。该表最重要的就是包含了一个指针数组,每个元素都是指向一个已打开的文件的指针。所以,文件描述符的本质上就是该数组的下标,这也解释了文件描述符会是一个小整数。所以拿着文件描述符,就可以找到对应文件。(可见下图)

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

(2) FILE 结构体

        C语言的stdio.h头文件中,定义了用于文件操作的结构体FILE,所以我们可以通过fopen返回一个文件指针(指向FILE结构体的指针)来进行文件操作。

        FILE结构体中最重要的两个成员变量是: 文件描述符和缓冲区的大小。其中C库的缓冲分三类:无缓冲、行缓冲、全缓冲。

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

        FILE结构体的部分成员:

struct FILE
{
    char *_ptr;//文件输入的下一个位置
    int _cnt;//当前缓冲区的相对位置
    char *_base;//指基础位置(文件的起始位置)
    int _flag;//文件标志
    int _file;//文件的有效性验证
    int _charbuf;//检查缓冲区状况,如果缓冲区则不读取
    int _bufsiz;//文件的大小
    char *_tmpfname;//临时文件名
};

 

        编写一段代码如下:

#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;
}

运行结果如下:

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

可以看到,当将程序结果输出到屏幕上只有三条语句;但是将结果写入文件时,有五条语句。原因是:

 

 

    1)一般C库函数写入文件是全缓冲的(等缓冲区慢或进程退出),而写到屏幕是行缓冲的;

    2)printf 、fwrite函数自带缓冲区,write函数无缓冲;

    3)所以写入文件时是行缓冲,放在缓冲区的数据不会被立即刷新,甚至在fork之后。而fork之后父进程刷新缓冲区的数据时,子进程也有一份同样的数据,随机产生两份数据。所以写入文件共五条数据。

二.编写简单的add/sub/mul/div函数,并打包成静/动态库,并分别使用

        这里只实现add函数及静/动态库,其他函数方法一样

 

(1)先实现简单函数,并测试代码是否正确

 

 

add.h(加法)

#pragma once

int add(int a, int b); 

 

add.c

#include "add.h"

int add(int a, int b)
{
    return a+b;
}

 

sub.h(减法)

#pragma once

int sub(int a, int b); 

 

sub.c

#include "sub.h"

int sub(int a, int b)
{
    return a-b;
}

 

mul.h(乘法)

#pragma once

int mul(int a, int b); 

 

mul.c

#include "mul.h"//乘法

int mul(int a, int b)
{
    return a*b;
}

 

div.h(除法)

#pragma once

int div(int a, int b); 

 

div.c

#include "div.h"

int div(int a, int b)
{
    return a/b;
}

main.c(测试函数)

 

#include <stdio.h>#include "add.c"int main(){ int a = 10; int b = 20; printf("add(10+20) = %d\n",add(10,20));

    printf("sub(20+10) = %d\n",sub(20,10));    printf("mul(20+10) = %d\n",mul(20,10));    printf("div(20+10) = %d\n",div(20,10)); return 0;}

 

运行结果如下:

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

(2)生成静态库

 

 

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

        因为静态库是在编译链接时,把库的代码就链接到了可执行文件中,所以程序运行的时候不再需要静态库。所以生成可执行文件后,删除静态库,程序一样运行。具体实现如下:

 

 

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

(3)生成动态库

 

 

(1)动态库的生成

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

(2)动态库的使用有三种方法

 

 

编译选项:

        l :链接动态库(只要库名即可)

        L :链接库所在的路径

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

        可以看到,采用链接静态库的方法是用不了动态库的。且动态库是在程序运行时才被链接的,多个程序共享使用库的代码,所以在生成可执行文件后,若删除所链接的库,可执行程序是运行不出来的。

 使用动态库有三种方法:

        1)拷贝.so动态库文件到系统共享库路径下,一般指/usr/lib

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

这里学一个命令 ldd,可以查看应用程序所依赖的动态库

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

 

 

         2)更改LD_LIBRARY_PATH

 

 

 

有些小可爱可能会遇到这样的问题:

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

可以用以下语句解决:

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

 

 

        该操作是安装了一个库,如果安装之后还出现这个问题,可以试试用-L指定以下查找路径为当前,应该是可以解决的。安装之后,编译链接执行如下:

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

 

        3)ldconfig配置/etc/ld.so.conf.d/,ldconfig更新

Linux基础IO-系统调用接口open/read等&文件系统&软硬链接&静/动态库

        

 

相关标签: Linux 基础IO