先来回忆一下C语言的文件操作:
#include
#include
int main()
{
//打开文件,以写的方式打开
FILE *fp = fopen("myfile", "w");
if(!fp){
printf("fopen error!\n");
}
//相应操作
const char *msg = "hello bit!\n";
int count = 5;
while(count--){
fwrite(msg, strlen(msg), 1, fp);
}
//关闭文件
fclose(fp);
return 0;
}
C默认会打开三个输入输出流,分别是stdin, stdout, stderr,这三个流的类型都是FILE, fopen返回值类型,文件指针*
操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现相应操作。
include <stdio.h>
#include
#include
#include
#include
#include
int main()
{
umask(0);
int fd = open("myfile", O_WRONLY|O_CREAT, 0644);
if(fd < 0){
perror("open");
return 1;
}
int count = 5;
const char *msg = "hello bit!\n";
int len = strlen(msg);
while(count--){
write(fd, msg, len);//fd: 文件描述符, msg:缓冲区首地址, //len:本次读取,期望写入多少个字节的数据。 返回值:实际写了多少字节数据
}
close(fd);
return 0;
}
open:
#include
#include
#include
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_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写
返回值:
成功:新打开的文件描述符
失败:-1
C语言的fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc),而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口。而系统调用为库函数的实现提供了底层支持。可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。
文件描述符就是系统调用接口open的返回值,它是一个整型。而Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入, 标准输出, 标准错误,分别用0,1,2来代表。所以,我们甚至可以这样输入和输出:
include <stdio.h>
#include
#include
#include
#include
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}
当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件
代码如下:
#include
#include
#include
#include
int main()
{
//以只读的方式打开文件
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
//打印文件描述符后关闭文件
printf("fd: %d\n", fd);
close(fd);
return 0;
}
文件描述符的结果是3,我们知道0,1,2分别代表标准输入,标准输出和标准错误。如果我们打开文件前,关掉标准输入0,会发生什么呢:
#include
#include
#include
#include
int main()
{
close(1);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}
我们会发现其结果变为了0,所以,文件描述符的分配规则就是:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。
代码如下:
#include
#include
#include
#include
#include
int main()
{
close(1);
int fd = open("myfile", O_WRONLY|O_CREAT, 0644);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
//刷新缓冲区
fflush(stdout);
close(fd);
exit(0);
}
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,其中,fd=1。这种现象叫做输出重定向。
因为我们知道,文件描述符就是指向对应打开文件的指针数组的下标,同时根据文件描述符的分配原则,关闭1以后,再打开文件分配的文件描述符就是1,所以本该输出到屏幕的内容,输出到了文件中去。
为了实现重定向,Linux系统可以使用dup2系统调用来实现重定向:
#include
//oldfd为要定向到的那个文件描述符
//newfd表示要被重定向的那个文件描述符
int dup2(int oldfd, int newfd);
有这样一段代码:
#include
#include
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;
}
运行结果如下:
但如果对进程实现输出重定向呢? ./hello > file , 我们发现文件结果变成了这样:
怎么变成五行了呢?虽然很迷,但是盲猜和fork有关!
这是因为printf 和 fwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。
printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,由C标准库提供。
在我们使用ls -l命令时,看到的除了看到文件名,还看到了文件的属性信息。
- | 常规文件,即file |
---|---|
d | 目录文件 |
b | block device 即块设备文件,如硬盘;支持以block为单位进行随机访问 |
c | character device 即字符设备文件,如键盘支持以character为单位进行线性访问 |
l | symbolic link 即符号链接文件,又称软链接文件 |
p | pipe 即命名管道文件 |
s | socket 即套接字文件,用于实现两个进程进行通信 |
通过stat可以查看文件时间记录的信息:
stat log.txt
通过 ls -i就可以查看文件的inode
Linux下每个文件都有自己的inode,Linux系统内部不使用文件名,而是使用inode号码识别文件。
Linux ext2文件系统,磁盘是典型的块设备,硬盘分区被划分为一个个的block。一个block的大小是由格式化的时候确定的,并且不可以更改。
根据不同区块功能的划分,创建文件需要四个步骤:
目录文件的文件内容存储的是文件名和inode指针的对应关系。同时对应文件的文件名信息并没有放在inode结构中,而是放在了文件所在的目录下。
硬链接通俗的将,就是给文件取了一个别名,创建一个文件的硬链接时,文件名对应的Inode依然是相应文件的Inode,同时删除文件时,对应的硬链接数量会减少。
而软连接是创建一个新的文件,Inode也是一个新的Inode,但是运行软连接时会引用对应的链接文件。
生成静态库代码如下:
[root@localhost linux]# ls
add.c add.h main.c sub.c sub.h
[root@localhost linux]# gcc -c add.c -o add.o
[root@localhost linux]# gcc -c sub.c -o sub.o
生成静态库
[root@localhost linux]# ar -rc libmymath.a add.o sub.o
ar是gnu归档工具,rc表示(replace and create)
查看静态库中的目录列表
[root@localhost linux]# ar -tv libmymath.a
rw-r--r-- 0/0 1240 Sep 15 16:53 2017 add.o
rw-r--r-- 0/0 1240 Sep 15 16:53 2017 sub.o
t:列出静态库中的文件
v:verbose 详细信息
[root@localhost linux]# gcc main.c -L. -lmymath
-I 指定头文件路径
-L 指定库路径
-l 指定库名
测试目标文件生成后,静态库删掉,程序照样可以运行。
库搜索路径:
生成动态库:
[root@localhost linux]# gcc -fPIC -c sub.c add.c
[root@localhost linux]# gcc -shared -o libmymath.so *.o
[root@localhost linux]# ls add.c add.h add.o libmymath.so main.c sub.c sub.h sub.o
shared: 表示生成共享库格式
fPIC:产生位置无关码(position independent code)
库名规则:libxxx.so
编译选项:
l:链接动态库,只要库名即可(去掉lib以及版本号)
L:链接库所在的路径.
当我们打包完成静态库时,是可以直接运行程序的。但是动态库则不然,需要做如下操作(任意一种都可以):