文章目录
文件IO
1. 文件概述
文件IO是从用户空间角度考虑的输入与输出,从内核读取数据或从文件中读取数据,写数据到内核或写数据到文件中。
内核中有很多文件,应该写到哪一个文件中呢,或从哪一个文件中读呢?(内核应该负责管理这些文件-文件管理)
因此在写入或读出之前用户应该指定某个文件,即要创建或打开某个文件
即:read ,write 这二个函数应该有一个参数是指定某个文件
read或write 函数之前有一个函数: open
操作之后,还要关闭这些文件 close
2. 相关函数
open
read
write
close
- open – 打开或创建一个文件
open(char *, flag, mode)在fcntl.h文件中声明。函数的作用:创建或打开某个文件,参数:最多有三个参数;
第一个参数,char * 包含有文件名和路径
第二个参数:flag 打开文件的方式
第三个参数:mode 创建文件的权限。(如果是打开文件不需要这个参数,如果创建一个文件需要这个参数)
flag内容如下:
flag | 功能 |
---|---|
O_RDONLY | 只读 |
O_WRONLY | 只写 |
O_RDWR | 读写 |
O_CREAT | 创建一个文件 |
O_EXCL | 如果使用O_CREAT时文件存在,则可返回错误消息。这一 参数可测试文件是否存在。 |
O_TRUNC | 打开文件(会把已经存在的内容给删除)。 |
O_APPEND | 追加方式打开文件(不会把已经存在的内容给删除)。 |
返回值:
成功:文件描述符,它是一个非负的正整数,即文件的ID号,相当于人的身份证号;
出错:-1。
open 函数创建文件时的权限是:
== mode & (~umask)
B111 111 111 & ~(B 000 010 010) = B 111 101 101
也可以直接umask xxx,更改umask.
什么是文件描述符?
内核的一个重要功能是文件管理,系统有非常多的文件,内核怎样认识每一个文件呢?内核采用ID号的方式标识这些文件,inode 号,inode号表示不同的文件,比如ls –lai,只要文件不一样,inode号就不一样。
那么这些内核的文件的ID号,在每个用户的程序中怎样映射的呢?即是文件描述符。
open函数的返回值就是这个ID号
ID号有什么规律呢?
从0开始累加,
程序进行时(进程),内核会自动打开3个文件描述符 0,1,2,分别对应,标准输入、输出和出错,这样在程序中,每打开一个文件,文件描述符值从3开始累加。
- write(int fd, void *buf, size_t count )
第一个参数:文件描述符,向哪一个文件中去写;
第二个参数:向这个文件中写什么内容;
第三个参数:向这个文件中写多少个。
函数的返回值:是实际写的字节数。
返回值:是实际写的字节数
- read(int fd, void *buf, size_t count)
第一个参数:文件描述符,从哪一个文件中去读;
第二个参数:读到什么地方去;
第三个参数:读多少个。
函数的返回值:是实际读的字节数。
返回值:是实际读的字节数
- lseek(int fd, off_t offset, int whence),该函数的头文件:sys/types.h unistd.
功能:调整读写的位置指针;
第一个参数:要调整的文件的文件描述符;
第二个参数:偏移量,每一读写操作所需要移动的距离,单位是字节的数量,可正可负(向前移,向后移);
第三个参数:当前位置的基点,有三个标志,
SEEK_SET:当前位置为文件的开头,新位置为偏移量的大小;
SEEK_CUR:当前位置为文件指针的位置,新位置为当前位置加上偏移量。
SEEK_END:当前位置为文件的结尾,新位置为文件的大小加上偏移量的大小。函
数的
返回值:成功:文件当前的位置,出错:-1。
- close(fd)
调用close()函数可以关闭一个打开的文件。
调用成功返回0,出错返回-1,并设置errno;
注:当一个进程终止时,该进程打开的所有文件都由内核自动关闭;
例子:拷贝文件==》cp命令的实现
创建两个文件一个a.c一个b.c。
a. c有内容,b.c是空文件。
#include "stdio.h"
#include "unistd.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
int main(int argc,char *argv[])
{
int rd_fd,wr_fd;
char read_buf[128]={0};
int rd_ret=0;
if(argc<3)
{
printf("please input src file and des file\n");
return -1;
}
rd_fd=open(argv[1],O_RDONLY);
if(rd_fd<0)
{
printf("open src file %s failure\n",argv[1]);
return -2;
}
printf("open src file %s success,rd_fd=%d\n",argv[1],rd_fd);
wr_fd=open(argv[2],O_WRONLY);
if(wr_fd<0)
{
printf("open src file %s failure\n",argv[2]);
return -3;
}
printf("open des file %s success,wd_fd=%d\n",argv[2],wr_fd);
while(1)
{
rd_ret=read(rd_fd,read_buf,128);
if(rd_ret<128)
break;
write(wr_fd,read_buf,rd_ret);
memset(read_buf,0,128); // clean cache
}
write(wr_fd,read_buf,rd_ret);
close(rd_fd);
return 0;
}
a.c内容如下:
操作过程:
标准IO
1 简介
与文件IO的区别?
文件IO:是直接调用内核提供的系统调用函数, 头文件是unistd.h
标准IO:是间接调用系统调用函数,头文件是: stdio.h
之前学过:输入输入相关的函数,都是和标准的输入(键盘),标准的输出(显示器)
标准IO中的相关函数,不仅可以读写普通文件,也可以向标准的输入或标准的输出中读或写。
2 测试验证库缓存的存在
三个缓存的概念(数组):
- 我们的程序中的缓存,就是你想从内核读写的缓存(数组)----用户空间的缓存
- 每打开一个文件,内核在内核空间中也会开辟一块缓存,这个叫内核空间的缓存
文件IO中的写即是将用户空间中的缓存写到内核空间的缓存中。
文件IO中的读即是将内核空间的缓存写到用户空间中的缓存中。 - 标准IO的库函数中也有一个缓存,这个缓存称为----库缓存
标准IO库函数在什么时候会调用系统调用函数?
标准IO库缓存写满或满足一定条件时,会调用系统调用函数。
int main()
{
char buf[]="hello linux";
printf("%s",buf);
while(1);
return 0;
}
这些都可以通过编译调试,看看是否有输出“hello linux”,观察现象。下面的都是类似。
printf满足一定条件 : 遇到\n 时,即会将库缓存的内容写到内核中,即调用了系统调用函数。
#include "stdio.h"
int main()
{
char buf[]="hello linux\n";
printf("%s",buf);
while(1);
return 0;
}
库缓存写满时,会调用系统调用函数,将lib_buffer 内容写到kernel_buffer中去
#include "stdio.h"
int main()
{
char buf[]="hello linux";
int i=0;
while(i< 93)
{
printf("%s",buf);
i++;
}
printf("h");
while(1);
return 0;
}
3 相关函数
文件IO | 标准IO |
---|---|
open | fopen |
close | fclose |
lseek | fseek, rewind |
read | 读函数比较多(分三类,全缓存、行缓存和无缓存) |
write | 读函数比较多(分三类,全缓存、行缓存和无缓存) |
- FILE *fopen (const char *path, const char *mode)
返回值:FILE * 文件流指针 类似于文件IO 中的文件描述符
FILE 定义:struct _IO_FILE,在/usr/include/libio.h
包含读写缓存的首地址、大小、位置指针等。
标准的输入流:stdin 0
标准的输出流:stdout 1
标准的出错流:stderr 2
Mode 类似于文件IO中的 flag
b | 二进制文件 |
---|---|
r | 只读方式打开文件,文件必须存在; |
w或a | 只写方式打开文件,文件不存在则创建;区别:w等价O_TRUNC,a等价O_APPEND; |
+ | 读写方式打开文件,文件必须存在; |
例:以读写方式打开一个文件,该文件必须存在: r+
以追加方式打开一个文件,若文件不存在,则创建: a或a+
- int fclose(FILE *stream)
fclose()调用成功返回0,失败返回EOF,并设置errno
在该文件被关闭之前,刷新缓存中的数据。如果标准 I/O 库已经为该流自动分配了一个缓存,则释放此缓存。
下面看一个例子:
打开一个文件,如果没有就创建一个。
#include "stdio.h"
int main(int argc, char *argv[])
{
FILE *fp;
fp=fopen(argv[1],"a+");
if(fp=NULL)
{
printf("creat file %s failure\n",argv[1]);
return -1;
}
printf("creat file %s success\n",argv[1]);
fclose(fp);
return 0;
}
- 读写函数:
三类读写函数:
一类:行缓存 遇到新行符(\n) 或写满缓存时,即调用系统调用函数
读:fgets, gets, printf, fprintf,sprintf
写:fputs, puts,scanf
一个字符的读写,是否是行缓存?
读:fgetc, getc, getchar
写:fputc, putc,putchar
二类:无缓存 只要用户调这个函数,就会将其内容写到内核中
三类:全缓存 只有写满缓存再调用系统调用函数
读:fread
写:fwrite
3.1 行缓存的读写函数fgets
和fputs
char *fgets (char *s, int size, FILE *stream)
第一个参数:缓存,即读到哪里去
第二个参数:读多少个字节
第三个参数:从什么地方读
返回值若成功则为s(缓存的地址),若已处文件尾端或出错则为null
int fputs(const char *s,FILE *stream);
第一个参数:缓存,即写什么内容
第二个参数:写到哪里去
若成功则为非负值,若出错则为EOF -1
。
3.2 刷新缓存函数:fflush(FILE *fp)
把库函数中的缓存的内容强制写到内核中。
3.3 无缓存:stderr
stdout
行缓存
3.4 调整读写位置指针函数:
fseek()
参数与lseek
是一样的但是返回值不一样
lseek
的返回值是:当前文件的位置指针值;
fseek()
的返回值是:成功返回0,失败返回-11;
rewind(FILE *fp)
用于设定流的文件位置指示为文件开始,该函数调用成功无返回值。
rewind()
等价于(void)fseek(fp 0, SEEK_SET)
;
ftell(FILE *fp)
用于取得当前的文件位置,调用成功则为当前文件位置指示,若出错则为-1L;
3.5 行缓存的读写函数gets
和puts
char *gets(char *s);
int puts(const char *s);
gets
与fgets
的区别:
gets()
时不能指定缓存的长度,这样就可能造成缓存越界(如若该行长于缓存长度),写到缓存之后的存储空间中,从而产生不可预料的后果;
gets()
只能从标准输入中读;
gets()
与fgets()
的另一个区别是: gets()
并不将新行符存入缓存中, fgets
将新行符存入缓存中;
puts
与fputs
的区别:
puts()
只能向标准输出中写;
puts()
与fputs()
的另一个区别是: puts
输出时会添加一个新行符, fputs
不会添加;
例如下面这个例子:
#include "stdio.h"
#include "string.h"
int main()
{
char buf[128] = {0};
int len;
puts("gets()输入:");
gets(buf);
len=strlen(buf);
printf("len=%d\n",len);
fputs("fgets()输入:",stdin);
fgets(buf,128,stdin);
len=strlen(buf);
printf("len=%d\n",len);
puts(buf);
return 0;
}
输出结果显示:
3.6 fprintf、printf、sprintf
行缓存的函数
int fprintf(FILE *stream,”字符串格式”)
fprintf
可以输出到文件中,也可输出到显示器,
printf
只能输出到显示器中。
int sprintf(str *, “字符串格式”)
输出内容到一个字符串中
3.7 一个字符读写函数fgetc
和fputc
int fgetc(FILE *fp)
功能:从文件中读取一个字符;
参数:文件流
返回值:正确为读取的字符,到文件结尾或出错时返回EOF。
int fputc(int c, FILE *fp)
功能:写一个字符到文件中
参数:第一个参数为要写的字符,第二个参数为文件流
返回值:成功则返回输入的字符,出错返回EOF。
fputc有缓存,但不是行缓存函数
证明:
#include "stdio.h"
int main(int argc,char *argv[])
{
FILE *fp;
fp=fopen("./a.c","w+");
if(fp==NULL)
{
printf("open file a.c failure\n");
return -1;
}
printf("open file a.c success\n");
// fputc('a',fp);
// fputc('\n',fp);
// fflush(fp);
while(1);
fclose(fp);
return 0;
}
将注释的先后打开,观察现象。
char *fgets (char *s, int size, FILE *stream)
返回值若成功则为s(缓存的地址),若已处文件结尾或读错则为null
int fgetc(FILE *fp)
返回值:正确为读取的字符,到文件结尾或读错时返回EOF。
3.8 int feof(FILE *stream)
功能:判断是否已经到文件结束
参数:文件流
返回值:到文件结束,返回为非0,没有则返回0
3.9 int ferror(FILE *stream)
;
功能:判断是否读写错误
参数:文件流
返回值:是读写错误,返回为非0,不是则返回0
3.10 void clearerr(FILE *stream)
;
功能:清除流错误
参数:文件流
例子:
#include "stdio.h"
#include "string.h"
int main(int argc,char *argv[])
{
FILE *fp;
char *ret;
char buf[]="hello linux\n";
char readbuf[128]={0};
fp=fopen("./a.c","w+");
if(fp==NULL)
{
printf("open file a.c failure\n");
return -1;
}
printf("open file a.c success\n");
fputs(buf,fp);
rewind(fp);
fgets(readbuf,128,fp);
printf("readbuf:%s",readbuf);
memset(readbuf,0,128); //clear memory
ret=fgets(readbuf,128,fp);
printf("seconf readbuf=%s,ret=%p\n",readbuf,ret);
printf("feof=%d,ferror=%d\n",feof(fp),ferror(fp));
clearerr(fp); // clear error
printf("clear error after:feof=%d,ferror=%d\n",feof(fp),ferror(fp));
fclose(fp);
return 0;
}
输出结果:
实验:cat命令的实现
linux内核实现cat命令的架构:
下面通过fputc/fgetc来实现:
#include "stdio.h"
int main(int argc,char *argv[])
{
FILE *src_fp;
int read_ret;
if(argc < 2)
{
printf("please input src file\n");
return -1;
}
src_fp=fopen(argv[1],"r");
if(src_fp == NULL)
{
printf("open file file %s failure\n",argv[1]);
return-2;
}
printf("open src file %s success\n",argv[1]);
// start read write
while(1)
{
read_ret=fgetc(src_fp);
if(feof(src_fp))
{
printf("read file %s end\n",argv[1]);
break;
}
fputc(read_ret,stdout); // 当一个进程打开时,内核自动打开三个文件(输入,输出,错误)
}
fclose(src_fp);
return 0;
}
通过fputs/fgets来实现:
#include "stdio.h"
#include "unistd.h"
#include "fcntl.h"
#include "string.h"
int main(int argc,char *argv[])
{
FILE *src_fp;
int read_ret;
char read_buf[128];
char *buf;
int i=0;
if(argc < 2)
{
printf("please input src file\n");
return -1;
}
src_fp=fopen(argv[1],"r");
if(src_fp == NULL)
{
printf("open file file %s failure\n",argv[1]);
return -2;
}
printf("open src file %s success\n",argv[1]);
// start read write
while(1)
{
buf=fgets(read_buf,128,src_fp);
i=i+1;
printf("i=%d\n",i);
if(feof(src_fp))
{
printf("read file %s end\n",argv[1]);
break;
}
fputs(read_buf,stdout);
}
fclose(src_fp);
return 0;
}
3.11 全缓存的二个函数:
-
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
-
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
第一个参数 buf:写的内容
第四个参数 fp:写到哪里去
第二个参数 size:写的内容中,每一个单元所占的字节数
第三个参数 nmemb:写的内容中,有多少个单元数
总共写多少个字节,size*nmemb
比如定义了一个char buf[128],那么这里就是size就是1(char占一个字节),nmemb就是128。
返回值:实际写的单元数。
实验:
#include "stdio.h"
int main(int argc,char *argv[])
{
FILE *fp;
char buf[]="hello linux";
char readbuf[128]={0};
fp=fopen(argv[1],"w+");
if(fp==NULL)
{
printf("open file a.c failure\n");
return -1;
}
printf("open file a.c success\n");
fwrite(buf,sizeof(char),sizeof(buf),fp);
rewind(fp); //fseek(fp,0,SEEK_SET);
fread(readbuf,sizeof(char),sizeof(readbuf),fp);
printf("readbuf:%s\n",readbuf);
fclose(fp);
return 0;
}
3.12
(1) fseek() 参数与lseek是一样的
但是返回值不一样
Lseek的返回值是:当前文件的位置指针值
fseek()的返回值是:成功返回0,失败返回-1,并设置errno
(2)ftell(FILE *fp)
用于取得当前的文件位置,调用成功则为当前文件位置指示,若出错则为-1L
(3) rewind(FILE *fp) 用于设定流的文件位置指示为文件开始,该函数调用成功无返回值。
rewind()等价于(void)fseek(fp 0, SEEK_SET)
(4) fflush(FILE *fp)
把库函数中的缓存的内核强制写到内核中。
Linux下静态库和动态库(共享库)的制作与使用
Linux操作系统支持的函数库分为:
静态库,libxxx.a
,在编译时就将库编译进可执行程序中。
优点:程序的运行环境中不需要外部的函数库。
缺点:可执行程序大
动态库,又称共享库,libxxx.so
在运行时将库加载到可执行程序中。
优点:可执行程序小。
缺点:程序的运行环境中必须提供相应的库。
静态库的制作:
- 生成目标文件:
gcc -c -o xxx.o xxx.c
- 将目标文件生成静态函数库,创建命令
ar
ar -cr -o xxx.a xxx.o
-c: create
的意思
-r: replace
的意思,表示当插入的模块file.o已经存在libfile.a中,则覆盖。反之ar显示一个错误消息。
操作静态库的几个实例:
情况1: 如果从别处得到一个静态库libunknown.a,想知道其中包含哪些模块。
命令:ar -t libunknown.a
静态库的编译:gcc -o main main.c -L. -lfile编译main.c就会把静态函数库整合进main。
其中:
-L指定静态函数库的位置供查找,注意L后面还有’.’,表示静态函数库在本目录下查找。
-l则指定了静态函数库名,由于静态函数库的命名方式是lib***.a,其中的lib和.a忽略。
删除libaddsub.a后main依然可以运行,因为静态库的内容已经整合进去了。
实验:
查看两个文件内容:
生成静态库:
动态函数库的制作:
-
生成目标文件:
gcc -c -o xxx.o xxx.c
-
gcc -shared -fpic -o libxxx.so xxx.o
-fpic
:产生位置无关代码。
-shared
:生成共享库。
用上述命令生成libaddsub.so
动态函数库。
gcc -o main main.c -L. -lxxx
此时还不能立即./main
,因为在动态函数库使用时,会查找/usr/lib
或 /lib
目录下的动态函数库,而此时我们生成的库不在里边。
第一种方法:
libaddsub.so
放到/usr/lib
或/lib
中去。
第二种方法,假设libxxx.so
在/home/llc/test/day2/2
环境变量方法
export LD_LIBRARY_PATH=/home/llc/test/day2/2
第三种方法:
在/etc/ld.so.conf
文件里加入我们生成的库的目录,然后ldconfig /etc/ld.so.conf
运行刚才修改的shell脚本(在root权限下运行)。
目录IO
相关函数
目录I/O与文件I/O函数的比较
目录I/O | 文件I/O |
---|---|
opendir | 只能打开目录 |
mkdir 创建目录 | open |
readdir 读目录 | read |
rewinddir 调整位置指针 | rewind |
telldir | ftell |
seekdir | fseek |
closedir:关闭目录 | close |
- opendir
DIR *opendir(const char *pathname)
参数:打开的目录以及路径
返回值:成功返回目录流指针,出错返回NULL。
int mkdir(const char * path, mode_t mode)
path为欲创建的目录文件路径,
mode为该目录的访问权限
返回值:若目录创建成功,则返回0;否则返回-1
生成的目录权限仍和umask有关系。
- readdir
struct dirent *readdir(DIR *dr)
参数:目录流指针
返回值:成功则为struct dirent指针,若在目录尾或出错则返回NULL。
struct dirent定义在头文件dirent.h中。
此结构至少包含下列两个成员:
struct dirent
{
ino_t d_ino; // inode 号
char d_name[NAME_MAX+1]; //文件名
}
- rewinddir:重置读取目录流的位置为开头
void rewinddir(DIR *dr)
参数:目录流指针
long telldir(DIR *dirp)
参数:目录流指针
返回值:目录流当前位置
void seekdir(DIR *dirp , long loc)
类似于文件定位函数fseek(),在目录流上设置下一个readdir()操作的位置。
参数:目录流指针和偏移量
- closedir
int close(DIR *dr)
参数:目录流指针
返回值:成功返回 0,出错返回- 1。
实验:
打开一个目录,并且将该目录下的子文件和子目录都读出来。
注意:目录下的子文件和子目录存放方式以链表的形式存放,每读取完一个链表,指针指向下一个链表,直到读取完成。用到的函数不知道怎么用,可以直接输入man xxx,查询帮组。
#include "stdio.h"
#include "sys/types.h"
#include "dirent.h"
int main(int argc,char *argv[])
{
int ret;
DIR *dp;
struct dirent *dir;
if(argc<2)
{
printf("please input open directory name\n");
return -1;
}
dp=opendir(argv[1]);
if(dp == NULL)
{
printf("open mydir failure\n");
return -2;
}
printf("open mydir success\n");
while(1)
{
dir=readdir(dp);
if(dir != NULL)
{
printf("inode=%ld,name=%s\n",dir->d_ino,dir->d_name);
}
else
break;
}
closedir(dp);
return 0;
}
综合实例:单机模式下的文件上传送和下载
(1) 输入服务器的地址: 路径和目录名
(2) 列出服务器中有哪些文件: opendir readdir
(3) 输入从服务器下载的文件名 或 上传文件到服务器的文件名
(4) 文件下载 或 文件上传送
文件IO: open read write close
标准IO fopen fputs fgets fputc fgetc fread fwrite fclose
第一实验:下载文件
#include "stdio.h"
#include "sys/types.h"
#include "dirent.h"
#include "string.h"
#include "unistd.h"
#include "fcntl.h"
int main()
{
DIR *dp;
int src_fd,des_fd;
int fd,ret;
struct dirent *dir;
char server[128]={0};
char file[128]={0};
char buf[128]={0};
start:
printf("please input server PATH and Directory name\n");
scanf("%s",server);
//list server files
dp=opendir(server);
if(dp==NULL)
{
printf("open server:%s faliure\n",server);
goto start;
}
printf("open server:%s success\n",server);
// show table
while(1)
{
dir=readdir(dp);
if(dir==NULL)
break;
else
{
printf("inode=%ld\t file_name=%s\n",dir->d_ino,dir->d_name);
}
}
printf("\n Please input download file\n");
scanf("%s",file);
//open server file
src_fd=open(strcat(strcat(server,"/"),file),O_RDONLY);
if(src_fd<0)
{
printf("open download file%s failure\n",file);
return -1;
}
printf("open download file%s success\n",file);
des_fd=open(file,O_CREAT|O_WRONLY,0777);
if(des_fd<0)
{
printf("creat file %s failure\n",file);
return -2;
}
printf("creat file %s success\n",file);
//content is written to the file
while(1)
{
ret=read(src_fd,buf,128);
write(des_fd,buf,ret);
if(ret<128)
break;
}
close(src_fd);
close(des_fd);
closedir(dp);
return 0;
}
第一实验:上传文件
#include "stdio.h"
#include "sys/types.h"
#include "dirent.h"
#include "string.h"
#include "unistd.h"
#include "fcntl.h"
int main()
{
DIR *dp;
int src_fd,des_fd;
int fd,ret;
struct dirent *dir;
char client[128]={0};
char file[128]={0};
char buf[128]={0};
start:
printf("please input client PATH and Directory name\n");
scanf("%s",client);
//list server files
dp=opendir(client);
if(dp==NULL)
{
printf("open client erver:%s faliure\n",client);
goto start;
}
printf("open client:%s success\n",client);
while(1)
{
dir=readdir(dp);
if(dir==NULL)
break;
else
{
printf("inode=%ld\t file_name=%s\n",dir->d_ino,dir->d_name);
}
}
printf("\n Please input upload file\n");
scanf("%s",file);
// open src file, only read
src_fd=open(strcat(strcat(client,"/"),file),O_RDONLY);
printf("please input des:\n");
// input absolute path,read and write way
scanf("%s",file);
des_fd=open(file,O_CREAT|O_WRONLY,0644);
if(des_fd<0)
{
printf("open client des file %s failure\n",file);
return -1;
}
printf("open client file success\n");
while(1)
{
ret=read(src_fd,buf,128);
write(des_fd,buf,ret);
if(ret<128) //return less 128 exit loop
break;
}
close(src_fd);
close(des_fd);
closedir(dp);
return 0;
}