Linux的文件结构:
在Linux中,几乎一切都可以看做是文件来处理,文件是操作系统与具体物理设备之间的一个简单而统一的接口。所以,我们在程序中可以像使用文件那样去读写磁盘、操作串口、打印机等其他设备。常用的文件操作函数有,open、read、write、lseek。
那么内核如何区分和引用特定的文件呢?那就是通过文件描述符啦,文件描述符其实就是一些小值整数,我们可以通过这些整数传递给文件操作函数从而达到访问到具体哪一个文件。简单的说,文件描述符是一个非负的整数,它是一个索引值,并指向内核中每个进程打开文件的记录表。当打开一个现有的文件或者新建一个文件时,内核就向进程返回一个文件描述符。当需要读写文件时,就把文件描述符作为参数传递给相应的文件操作函数。具体下面再讨论~~
通常来说,当一个进程运行时,都会自动打开三个文件:标准输入(键盘)、标准输出(屏幕)、标准出错处理(屏幕)。这三个文件对应的文件描述符分别为0、1、2.(对应的宏为STDIN_FILENO、STDOUT_FILENO、STDERR_FILENO)。
系统调用:
所谓的系统调用其实就是操作系统提供给用户程序调用的一组“特殊的接口”,用户程序可以通过这个特殊的接口获得操作系统内核提供的服务。那么,我们就有疑问了?为啥操作系统不直接让用户直接访问内核,还搞出个“特殊的接口”,这不多此一举吗?非也非也,Linux操作在安全方面考虑的比较周到,为了更好的保护内核空间,将程序运行空间分为用户空间(0-3G)和内核空间(3G-4G),它们分别运行在不同的等级上,在逻辑上是相互分离的。通常情况系,用户进程在通常情况下不允许访问内核数据,也就不能访问内核函数,它们只能操作用户数据,调用用户函数。
但是,在有些情况下,用户空间进程需要获得一定的系统服务(调用内核的函数),这时操作系统就必须利用系统提供给用户的“特殊接口”——调用系统规定用户进程进入内核空间的具体位置。进入系统调用时,程序运行空间需要从用户空间进入内核空间,处理完之后再返回用户空间。
库函数:
在输入输出操作中,直接使用底层系统调用的问题是它们的效率非常低。为什么啊?
(1)与函数调用相比,系统调用的开销明显要大,因为在执行系统调用的时,Linux必须从用户态转换到内核态,还要返回用户态。多费劲呐~~减少这种开销的方法是,在程序中尽量减少调用次数(废话、这谁都知道),并且让每次系统调用完成尽可能多的工作(不要每次只写一个字,读一个字,多累啊)(我们最好像读磁盘数据一样,一次性大批量的读取它。哈哈~~)
(2)硬件会对底层系统调用一次所能读写的数据块做出一定的限制。
为了给设备和磁盘文件提供一个更高层更有效的接口,Linux提供了一系列的标准函数库。这些函数库提供输出缓冲功能的标准IO库就是这样一个例子。我们可以高效的写任意长度的数据块,函数则在数据满足数据长度要求时安排执行底层系统调用(通俗的讲就是,比如:邮递员告诉你,你有邮件了快来拿,然后你屁颠屁颠的去拿,拿了就回家。不久之后,他又打电话告诉你,你有邮件快来拿,然后你再屁颠屁颠的去拿,拿了又回去。再不久之后,他又来电话告诉你,你又有邮件了快来拿吧,结果你说,你个屌丝能不能一次性三个邮件一起拿给我啊?)带缓冲功能的标准IO库就是一次性给你三个邮件的好邮递员啦~~
底层IO操作函数的介绍:
不带缓冲的IO操作又叫做底层IO操作。主要有5个函数:open()、close()、read()、write()、lseek()。这些函数 特点就是不带缓存,直接就对文件进行操作。(好不害羞的样子,直接就对人家、、、嘿嘿~~想多了你)
下面是对这个五个函数的一些详细介绍:
错误:返回 -1
--------文件I/O(系统调用)--------
最常用的文件操作系统调用
open() 打开文件
create() 创建文件
close() 关闭文件
read() 读取文件
write() 写入文件
lseek() 移动文件指针
fcntl()
access() 文件控制
open()系统调用
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
成功返回文件描述符fp,失败时返回-1.第一个参数是一个字符串参数,是要创建和打开的新文件的吗路径名。第二个参数指明了open操作需要创建一个新文件的“标志”。第三个参数指明了新创建的文件的访问权限。
open()系统调用的“标志”
O_RDONLY | 以只读方式打开文件 |
O_WRONLY | 以只写方式打开文件 |
O_RDWR | 以读写方式打开文件 |
O_APPEND | 以追加模式打开文件,在每次写入操作指向之前,自动将文件指针定位到文件末尾。但在网络文件系统进行操作时却没有保证。 |
O_CREAT | 如果指定的文件不存在,则按照mode参数指定的文件权限来创建文件。 |
O_DIRECTORY | 假如参数pathname不是一个目录,那么open将失败。 |
O_EXCL | 如果使用了这个标志,则使用O_CREAT标志来打开一个文件时,如果文件已经存在,open将返回失败。但在网络文件系统进行操作时却没有保证。 |
close()函数的语法:
所需头文件:#include<unistd.h> (头文件我们可以使用 man命令查看,如:man close)
函数原型:int close( int fd );
函数传入值:fd(open返回的文件描述符)
函数返回值:成功:0,失败:-1
read()函数的语法:
所需头文件:#include <unistd.h> (使用man 2 read 命令查看)
函数原型:ssize_t read(int fd,void *buf,size_t cout);
函数传入值:fd,文件描述符(想读哪个文件就传哪个文件描述符)
buf,存储ner的内存空间(读到哪去,就是读到buf里面来)
count,读取的字节数(想读多少)
函数返回值:成功:0,失败:-1
write()函数的语法:
所需头文件:#include <unistd.h> (使用man 2 write 命令查看)
函数原型:ssize_t write (int fd,void *buf,size_t cout);
函数传入值:fd,文件描述符(想对个文件进行写操作就传哪个文件描述符)
buf,存储ner的内存空间(把buf里面的内容写到fd相关联的文件里面去)
count,想写的字节数(想写多少)
函数返回值:成功:0,失败:-1
lseek()函数语法:
所需要头文件:#include <unistd.h>
#include <sys/types.h>
函数原型:ssize_t lseek(int fd,off_t offset,int whence);
函数传入值:fd,文件描述符;offset,偏移量;
whence(基点)可以取以下三种
SEEK_SET:文件开头+offset为新读写位置
SEEK_CUR: 目前读写位置+offset为新读写位置
SEEK_END:文件结尾+offset为新读写位置
函数返回值:成功:0,失败:-1
creat()系统调用
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int creat(const char *pathname, mode_t mode);
creat()相当于open使用了参数flags等于O_CREAT |O_WRONLY|O_TRUNC
access()系统调用
#include <unistd.h>
int access(const char *pathname, int mode);
pathname:文件路径名,
mode可以是以下值或其组合。
R_OK | 判断文件是否有读权限。 |
W_OK | 判断文件是否有写权限。 |
X_OK | 判断文件是否有可执行权限。 |
F_OK | 判断文件是否存在。 |
使用fcntl()系统调用修改文件属性:
Int fcntl(int fd,int cmd);
Int fcntl(int fd,int cmd,long arg);
Int fcntl(int fd,int cmd,struct flock *lock);
fd是文件描述符,第二个参数指定了函数的操作,函数功能如下:
复制一个文件描述符(cmd=F_DUPFD)
获取/设置文件描述符标志(cmd=F_GETFL或cmd=F_SETFD)
获取/设置文件描状态标志(cmd=F_GETFL或cmd=F_SETFL)
获取/设置文件锁(cmd=F_GETFK、cmd=F_SETLK或cmd=F_SETLEK)
--------标准I/O(库函数)--------
FILE *fopen(const char *path,const char *mode);
第一个参数就是文件的路径名
第二个参数表示文件的打开模式
Int fclose(FILE *fp);
当文件关闭成功是返回0,失败时返回一个非零值。
按字符读写文件:
Int fgetc(FILE *stream);
Int getc( FILE *stream);
读取正确返回读取字符的int值,失败返回EOF。
Int fputc(int c,FILE *stream);
Int putc(int c,FILE *stream);
写入成功返回读取字符的int值,失败返回EOF。
按字符串读写文件:
char *fgets(char *s,int size,FILE *stream);
该函数从文件中读入一行以‘\ 0’或EOF结尾的字符串。
Int fputs(const char *s,FILE *stream);
如果字符串被成功写入文件返回非负整数,失败返回EOF。
按数据块读些文件:
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);
第一个参数分别是要读写的数据的地址,
第二个参数是数据块的大小,
第三个参数是要读写的数据块的数目,
第四个参数是文件句柄。
当读写成功时返回成功读写的数据块的数目。
文件的格式化输入和输出:
Int fprintf(FILE *stream,const char *format, …);
Int fscanf(FILE *stream,const char *format, …);
文件的随机存取:
基于流的文件操作,每个打开的文件,也都有一个文件指针表明当前的存储位置。
Int fseek(FILE *stream,long off_set,int whence);
和lseek()函数类似。移动文件指针成功时返回0,失败返回-1.
long ftell(FILE *stream);函数可以获得当前指针的位置。
对文件指针操作的函数还有:
int fgetpos(FILE *stream,fpos_t *pos);
int fsetpos(FILE *stream,fpos_t *pos);
其可以获取和设置文件指针的位置,通过参数pos实现。
我自己编写测试程序时出现过得错误:
不加sleek的话,文件指针指在文件末尾,所以read返回值为0
加完之后就TM正常了