1.复习C语言阶段学习过的文件操作–>we文件流指针
1.一系列函数
FILE fopen(const char path,const char* mode**)
path:需要打开文件的路径加上文件的名称;可以不加路径,默认打开的是当前路径下的文件
mode:以什么方式打开文件
r:以读的方式打开,如果打开的文件不存在,则报错
r+:以读写的方式打开,如果打开的文件不存在,则报错
w:以写的方式打开,如果文件不存在则直接创建。如果存在,则将当前文件截断(将文件内容清空),文件流指针指向头部
w+:以读写方式打开,如果文件不存在则直接创建。如果存在,则将当前文件截断(将文件内容清空),文件流指针指向头部
a:以追加方式打开,并不能读文件。在文件的末尾进行写。如果文件不存在则创建。
a+:以追加方式打开,支持读文件。在文件的末尾进行写。如果文件不存在则创建。
ssize_t fread(const void ptr,size_t size,size_t nmemb,FILE stream)**
ptr:将读到的内容保存在ptrzhong
size:块的大小(单位是字节)
nmemb:块的个数(该参数值的含义为数值)
sizenmemb=字节
一般常用用法是:将size置位1,每一块的大小是1字节:需要读多少字节,则将块的个数设置为多少
stream:文件流指针,标识从哪里去读
返回值:返回成功读到的块的个数
ssize_t fwrite(const void ptr,size_t size,size_t nmemb,FILE stream)**
ptr:将读到的内容保存在ptrzhong
size:块的大小(单位是字节)
nmemb:块的个数(该参数值的含义为数值)
sizenmemb=字节
一般常用用法是:将size置位1,每一块的大小是1字节:需要读多少字节,则将块的个数设置为多少
stream:文件流指针,标识从哪里去读
返回值:返回成功读到的块的个数
int fseek(FILE* stream,long offset,int whence)
stream:文件流指针
offset:偏移量,针对whence而言的
whence:SEEK_SET文件首部 SEEK_CUR当前位置 SEEK_END文件尾部
*int fclose(FILE fp)fp:文件流指针
ulimit -a 命令可以查看open files:含义是单个进程可以打开文件的最大上限 eg: open files (-n)1024
由于每一个进程创建的时候,都会默认打开三个文件流:stdin,stdout,strerr
下面是一些代码案例
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
FILE* fp = fopen("tmp.file", "w+");
if(!fp)
{
perror("fopen");
return 0;
}
int ret = fseek(fp, -1, SEEK_SET);
printf("fseek ret = %d\n", ret);
perror("fseek");
const char* data = "linux-68";
ssize_t writesize = fwrite(data, 1, strlen(data), fp);
//ssize_t writesize = fwrite(data, 3, 4, fp);
//含义是写入了多少个块, 如果size是1,则可以认为是写入了多少个字节
printf("writesize: %lu\n", writesize);
fseek(fp, -4, SEEK_END);
//使用
char buf[1024];
memset(buf, '\0', sizeof(buf));
//ssize_t readsize = fread(buf, 2, 3, fp);
//printf("readsize: %lu\n", readsize);
//printf("readbuf: %s\n", buf);
ssize_t readsize = fread(buf, 1, 8, fp);
printf("readsize: %lu\n", readsize); //readsize = 8 这个时候我们可以认为是读了多少个字节,本质含义是读到了8个块,每个块为1字节;
printf("readbuf: %s\n", buf);
fclose(fp);
return 0;
}
2.系统调用文件操作–>文件描述符
int open(const char pathname,int flags,mode_t mode)*
pathname:待打开文件的路径+文件名称
flags:以何种方式打开
mode:对于新创建出来的文件,设置文件权限
返回值:返回文件描述符
ssize_t write(int fd,const void*buf,size_t count)
fd:文件描述符,在那里进行写
buf:写入的数据
count:写入数据的大小
sszie_t read(int fd,void* buf,size_t whence)
fd:文件描述符,从哪里进行读
buf:读到那里去
count:最大可以读多少字节,注意:需要预留\0的位置
off_t lssek(int fd,off_t offset,int whence)
fd:需要操作的文件描述符
offset:偏移量
whence:偏移到哪里去
close(int fd)
下面是一些代码案例:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
int main()
{
//flags:
// 以下三个选项当中是必选项,且三者当中只能选择一个
// O_RDONLY:以只读方式打开
// O_WRONLY:以只写方式打开
// O_RDWR : 可读可写方式打开
//以下为可选项,可以选择多个
// O_CREAT:如果不存在则创建文件
// O_TRUNC:截断文件(清空文件)
// O_APPEND:已追加方式打开文件
int fd = open("./tmp_file", O_RDWR | O_CREAT, 0664);
if(fd < 0)
{
perror("open");
return 0;
}
printf("fd= %d\n", fd);
int writesize = write(fd, "linux-68", 8);
if(writesize < 0)
{
perror("write");
return 0;
}
printf("writesize = %d\n", writesize);
lseek(fd, 0, SEEK_SET);
char buf[1024];
memset(buf, '\0', sizeof(buf));
int readsize = read(fd, buf, sizeof(buf) - 1);
if(readsize < 0)
{
perror("readsize");
return 0;
}
printf("readsize = %d\n", readsize);
printf("read buf is %s\n", buf);
close(fd);
while(1)
{
sleep(1);
}
return 0;
}
接下来解释一下什么是文件描述符
文件描述符其实可以看成一个正整数,它是内核当中维护的数组fd_array的下标,下标从0开始。当创建一个新进程的时候,默认打开三个文件描述符,分别是0(标准输入)1(标准输出)2(标准错误)。当程序员操作文件的时候,其实就是通过文件描述符找到fd_array数组当中对应的元素,每一个元素都对应一个文件信息,内核通过操作元素对应的文件信息来实现操作文件的。如下图:
文件描述符的分配规则:最小未占用原则
文件描述符与文件流指针的关系(着重理解缓冲区)
1.读写缓冲区,其实是在库函数当中维护的而非内核,当进程终止时,会刷新缓冲区,就是操作该缓冲区。_exit函数(系统调用)是内核代码,并不知道有缓冲区这个东西,而exit函数*库函数会刷新缓冲区。
2.为什么使用文件流指针的时候,由于打印的内容实现写到缓冲区当中去了,如果不加\n或者其他强制刷新的操作,就不会打印到屏幕上去了、
3.不管是使用文件流操作打开文件或者是打开标准文件流,都是在C库当中创建一个struct _IO_FILE结构体(每一个文件或者标准文件流,都对应一个struct_IO_FILE)如下图:
3.重定向
重定向的本质:将newfd文件描述符开呗oldfd文件描述符里去,这里介绍一个dup2函数,int dup2(int oldfd,int newfd)oldfd和newfd都是文件描述符
(1)newfd(标准输出)拷贝oldfd(tmpfile的文件描述符)
(2)当拷贝成功,则关闭newfd(标准输出)
(3)如果拷贝的oldfd是一个无效的文件描述符,则dup2什么事情都没有干,切记,没有关闭newfd
(4)如果拷贝的newfd和oldfd具有相同的值,则dup2什么事情都不干,没有关闭newfd
下面举一个例子,这里将标准输入的内容重定向到tmpfile里面去
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
//0 1 2
int fd = open("./tmpfile", O_RDWR | O_CREAT, 0664);
if(fd < 0)
{
perror("open");
return 0;
}
printf("fd = %d\n", fd);
//将标准输出 重定向到 tmpfile当中去
//3 : tmpfile
//1 : 标准输出
//需要被修改的是 标准输出
//模板的是 tmpfile
//int dup2(int oldfd, int newfd);
dup2(3, 1);
printf("hello-linux68\n");
return 0;
}
4.静态库和动态库
静态库:
1.win环境下的静态库的后缀是.lib,在win环境下编译一个依赖库文件的库执行程序,编译阶段依赖静态库(.lib),程序运行阶段,依赖是动态库(.dll)
2.静态库会将编译的代码当中的所有的函数全部编译到静态库当中去,静态链接指的是gcc+或者g++的命令行参数,静态库是一个库文件
3.-static:如果程序是静态链接产生的可执行程序,会将静态库中的所有代码全部编译到可执行程序当中去,当程序执行的时候,是不要依赖静态库的了
4.linux环境下,后缀为.a,前缀lib,eg:libctemplate.a,名称是去掉前缀和后缀剩下的就是库文件名称
5.静态库的生成:需要注意的是,生成静态库的时候,是使用.o文件来进行编译生成的 ar -rc lob[库文件名称].a [xxx].o
6.j静态库的使用:-L [path] 指定链接的库文件路径; -l[库文件名称(去了前缀和后缀的名称)]:指定链接库文件名称 eg:gcc main.c -o main -L [path] -l[库文件名称]
动态库:
1.win环境下动态库的后缀是.dll
2.linux环境下动态库的后缀是.so 前缀是lib
3.动态库的生成: -shared 生成共享库的明亮航参数 -fPIC:产生位置无关的代码
4.动态库的使用:-L [path] 指定链接的库文件路径;-l[库文件名称(去了前缀和后缀的名称)]:指定链接库文件的名称
如何让程序可以找到依赖的动态库:
1.将动态库放到当前可执行程序的目录下
2.在环境变量当中设置动态库的搜索路径,设置环境变量LD_LIBRARY_PATH(推荐使用这种方式)
3.直接将自己写的库或者第三方库文件和操作系统库文件放在一起(不推荐)
5.ext2文件系统举例
1.如何存储文件数据
a.将文件分成不同的block,从Block Bitmap当中去查找,查找Data blocks区域当中空闲的块,将文件存储不同的空闲块当中
b.需要对文件进行描述,从indoe Bitmap当中查找空闲的inode节点,从inode Table当中获取indoe节点,使用inode节点去描述文件存储信息
c.文件名称+inode节点号作为目录得目录项存储起来
2.如何获取文件数据
a.在目录当中根据文件名称和inode节点号,找到inode节点
b.根据inode信息,查找到文件在Data blocks当中的对应的块,将块组织起来,就获取到了文件信息
6.软硬链接
软链接:
创建:ln-s [源文件] [待创建出来的软链接文件名称]
注意:
1.创建出来的软链接文件,指向源文件,修改软链接文件或者源文件是一回事
2.软链接文件和源文件具有不同的inode节点
3.如果使用ll查看到软链接文件指向的源文件一直在闪烁,说明源文件不存在
4.不推家大家删除软链接文件指向的源文件,要删除就一起删除掉
硬链接:
创建:ln [源文件] [待创建出来的硬链接文件名称]
注意:创建出来的硬链接文件具有相同的inode节点信息,删除硬链接文件并不影响源文件