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更新

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值