C语言之文件系统编程

(一)系统调用
所有的操作系统都提供多种服务的入口点,程序由此向内核请求服务。这些可直接进入内核的入口点被称为系统调用。
为什么用户程序不能直接访问内核提供的服务?
在Linux中,为了更好地保护内核空间,程序的运行空间分为内核空间和用户空间(也就是常称的内核态和用户态),它们分别运行在不同的级别上,在逻辑上是相互隔离的。因此,用户进程在通常情况下不允许访问内核数据,也无法使用内核函数,它们只能在用户空间操作用户数据,调用用户空间的函数。
(二)文件的概念
文件”这个名词不陌生,什么是文件?
系统资源(内存、硬盘、一般设备、进程间通信的通道等)的一个抽象
对系统资源进行访问的一个通用接口。
采用这种“文件”的方式有什么好处?
对资源提供通用的操作接口,可以极大地简化系统编程接口的设计。
 常见的文件类型(可以通过文件来访问的系统资源)有:
 普通文件:
  一般意义上的文件,作为数据存储在磁盘中,可以随机访问文件的内容。Linux系统中的文件是面向字节的,文件的内容以字节为单位进行存储和访问。
 目录:
 目录是一种特殊的文件,目录可以像普通文件一样打开、关闭以及进行相应的操作。
 管道:
 管道是Linux中的一种进程间通信的机制
 设备文件:
 设备文件没有具体的内容,对设备文件的读写操作实际上与某个设备的输入输出操作关联在一起。
 符号链接:
 符号链接的内容是指向另一个文件的路径。当对符号链接进行操作时,系统会根据情况将这个操作转移到它所指向的文件上去,而不是对它本身进行操作。
 socket:
  socket也是一种进程间通信的方式,与管道不同的是,它们可以在不同的主机上进行通信,也就是网络通信。
3)文件描述符
所有执行I/O操作的系统调用使用文件描述符来表示打开的文件。
 文件描述符是一个非负整数。
 文件描述符可以表示各种类型的打开的文件。
 对文件的操作只要使用文件描述符即可指定所操作的文件
4)文件操作的一般过程
打开文件,打开成功后,应用程序将获得文件描述符。
 应用程序使用文件描述符对文件进行读写等操作。
 全部操作完毕后,应用程序需要将文件关闭以释放用于管理打开文件的内存。
5)open()系统调用可以打开或创建一个文件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Returns file descriptor on success, or –1 on error    
 各参数及返回值的含义如下:
 pathname:要打开或创建的文件名称。
 flags:标志位,指定打开文件的操作方式。
 mode:指定新文件的访问权限。(仅当创建新文件时才使用该参数)
 返回值:若成功返回文件描述符,否则返回-1并设置变量errno的值。
基本取值:
 O_RDONLY:以只读方式打开文件。
 O_WRONLY:以只写方式打开文件。
 O_RDWR:以读写方式打开文件。
注意:上述三个常量必须指定且只能指定一个。
 可选取值(部分):
 O_CREAT:如果被打开的文件不存在,则自动创建这个文件。
 O_EXCL:如果O_CREAT标志已经使用,那么当由pathname参数指定的文件已经存在时,open返回失败。
 O_TRUNC:如果被打开的文件存在并且是以可写的方式打开的,则清空文件原有的内容。
 O_APPEND:新写入的内容将被附加在文件原来的内容之后,即打开后文件的读写位置被置于文件尾。
 思考:这些参数如何组合使用?
 6)read()系统调用从打开的文件中读取数据。
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
Returns number of bytes read, 0 on EOF, or –1 on error    
    各参数及返回值的含义如下:
 fd:要读取的文件的描述符。
 buf:读取到的数据要放入的缓冲区。
 count:要读取的字节数。
 返回值:若成功返回读到的字节数,若已到文件结尾则返回0,若出错则返回-1并设置变量errno的值。
注意:
1. 这里的size_t是无符号整型,ssize_t是有符号整型。
2. buf指向的内存空间必须事先分配好。
7)write()系统调用向打开的文件写数据。
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
Returns number of bytes written, or –1 on error    
    各参数及返回值的含义如下:
 fd:要写入的文件的描述符。
 buf:要写入的数据所存放的缓冲区。
 count:要写入的字节数。
 返回值:若成功返回已写的字节数,出错则返回-1并设置变量errno的值。
8) close()系统调用关闭一个打开的文件。
#include <unistd.h>
int close(int fd);
Returns 0 on success, or –1 on error    
    各参数及返回值的含义如下:
 fd:要关闭的文件的描述符。
 返回值:若成功返回0,出错则返回-1。
注:当一个进程终止时,内核会自动关闭它所有打开的文件。
9)lseek系统调用可以改变文件偏移量(File Offset)。文件偏移量是一个整数,表示距文件起始处的字节数。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fildes, off_t offset, int whence);
Returns new file offset if successful, or –1 on error
    其中,参数whence必需是以下三个常量之一:
SEEK_SET:将文件偏移量设置在距文件开始处offset个字节。
SEEK_CUR:将文件偏移量设置在其当前值加offset,offset可正可负。
SEEK_END:将文件偏移量设置为文件长度加offset,offset可正可负。

10)I/O缓冲
 为了提高系统进行I/O操作的效率!
 系统调用要请求内核的服务,会引发CPU模式的切换,期间会有大量的堆栈数据保存操作,开销比较大。如果频繁地进行系统调用,会降低应用程序的运行效率。有了缓  冲机制以后,多个读写操作可以合并为一次系统调用,减少了系统调用的次数,将大大提高程序的运行效率。
 所谓的标准I/O函数实际上是对底层系统调用的封装,最终读写设备或文件的操作仍需调用系统I/O函数来完成。
11)文件指针和流
标准I/O函数并不直接操作文件描述符,而是使用文件指针。文件指针和文件描述符是一一对应的关系,这种对应关系由标准I/O库自己内部维护。文件指针指向的数据类型为FILE型,但应用程序无须关心它的具体内容。
 在标准I/O中,一个打开的文件称为流(stream),流可以用于读(输入流)、写(输出流)或读写(输入输出流)。每个进程在启动后就会打开三个流,分别对应:stdin(标准输入流)、stdout(标准输出流)以及stderr(标准错误输出流)。
 12)fopen用于打开一个标准I/O流
#include <stdio.h>
FILE *fopen(const char *path, const char *mode);
fopen打开由path指定的文件,并把它与一个文件流关联起来。mode参数指定文件的打开方式。
fopen执行成功返回一个非空的FILE*指针,失败时返回NULL。
"r"或"rb":以只读方式打开。
 "w"或"wb":以只写方式打开,并把文件长度截短为零。
 "a"或"ab":以写方式打开,新内容追加在文件尾。
 "r+"或"rb+"或"r+b":以更新方式打开(读和写)。
 "w+"或"wb+"或"w+b":以更新方式打开,并把文件长度截短为零。
 "a+"或"ab+"或"a+b":以更新方式打开,新内容追加在文件尾。
注:字母b表示文件是一个二进制文件而不是文本文件。
13)fread()用于从一个文件流里读取数据。
#include <stdio.h>
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);
  数据从文件流stream读到ptr指向的数据缓冲区里。size参数指定每个数据记录的长度,nmemb给出要传输的记录的个数。函数的返回值是成功读到数据缓冲区里的记录的个数(不是字节数)。
14)fwrite()从指定的数据缓冲区里取出数据记录,并把它们写到输出流中。它的返回值是成功写入的记录个数。函数原型如下:
#include <stdio.h>
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
15) fclose()函数用于关闭指定的文件流。
#include <stdio.h>
int fclose(FILE *fp);
16)  fseek()用于在文件流里为下一次读写操作指定位置。
#include <stdio.h>
int fseek(FILE *stream, long offset, int whence);  
offset和whence参数的含义和取值与lseek函数完全一样,但lseek返回的是一个off_t数值,而fseek返回的是一个整数:0表示成功,-1表示失败并设置errno指出错误。
17) fgetc()从文件流里取出下一个字节并把它作为一个字符返回。当它到达文件结尾或出现错误时,返回EOF。getc()和fgetc()一样,但它有可能被实现为一个宏。getchar()相当于getc(stdin)。
#include <stdio.h>
int fgetc(FILE *stream);
int getc(FILE *stream);
int getchar(void);
18) fputc()把一个字符写到一个输出文件流中,它返回写入的值,如果失败,则返回EOF。类似fgetc()和getc(),putc()的作用也相当于fputc(),但它可能被实现为一个宏。putchar()相当于putc(c, stdout),它把单个字符写到标准输出。
#include <stdio.h>
int fputc(int c, FILE *stream);
int putc(int c, FILE *stream);
int putchar(int c);
注意:putchar和getchar都是把字符当作int类型而不是char类型来使用的,这就允许文件结尾EOF取值为-1。

发布了75 篇原创文章 · 获赞 45 · 访问量 5万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章