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

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

1. open函数

(1)函数原型
(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)函数原型
(2)函数功能
        从文件里读数据。
(3)参数

        1)fd :要读取数据的文件的文件描述符;
        2)buf :指缓冲区,要 有一个缓冲区接收读取的数据;
        3)count :表示调用一次read,应该读取多少数量的字符。

(4)返回值:
        返回读取到的字符数:0表示读到EOF,-1表示出错。
3. write 函数
(1)函数原型
(2)函数功能
        向目标文件里写内容
(3)参数

        1)fd :要写入的文件的文件描述符;
        2)buf :要向文件里写入的内容;
        3)count :写入多少内容。
(4)返回值

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

(1)函数原型
(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;
}
运行结果如下:
其中,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;
}

运行结果如下:

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。该表最重要的就是包含了一个指针数组,每个元素都是指向一个已打开的文件的指针。所以,文件描述符的本质上就是该数组的下标,这也解释了文件描述符会是一个小整数。所以拿着文件描述符,就可以找到对应文件。(可见下图)
(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;
}
运行结果如下:
可以看到,当将程序结果输出到屏幕上只有三条语句;但是将结果写入文件时,有五条语句。原因是:

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

运行结果如下:
(2)生成静态库

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

(3)生成动态库

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

编译选项:
        l :链接动态库(只要库名即可)
        L :链接库所在的路径
        可以看到,采用链接静态库的方法是用不了动态库的。且动态库是在程序运行时才被链接的,多个程序共享使用库的代码,所以在生成可执行文件后,若删除所链接的库,可执行程序是运行不出来的。
 使用动态库有三种方法:
        1)拷贝.so动态库文件到系统共享库路径下,一般指/usr/lib
这里学一个命令 ldd,可以查看应用程序所依赖的动态库

         2)更改LD_LIBRARY_PATH

有些小可爱可能会遇到这样的问题:
可以用以下语句解决:

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

        3)ldconfig配置/etc/ld.so.conf.d/,ldconfig更新
        这种方法我也不是特别理解,所以不能给大家详细的解释。

猜你喜欢

转载自blog.csdn.net/lycorisradiata__/article/details/79833052