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更新
上一篇: Mybatis终极案例之注解开发