moc 发表于 2018-10-27 12:30:48

006-文件I/O

本帖最后由 moc 于 2018-10-27 21:34 编辑

1、C标准函数与系统函数的区别
C库函数的角度理解文件的基本操作:

深入到C库函数需要向下调用内核提供的系统函数进入到内核态:

C标准库、系统API、硬件层之间的关系:

文件描述符
一个进程默认打开3个文件描述符:
        ① STDIN_FILENO0
        ② STDOUT_FILENO1
        ③ STDERR_FILENO 2
新打开文件返回文件描述符表中未使用的最小文件描述符。

2、open/close
open函数
open函数可以打开或创建一个文件; 返回值:成功返回新分配的文件描述符,出错返回-1并设置errno
#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);      pathname参数是要打开或创建的文件名,flags参数有一系列常数值可供选择,可以同时选择多个常数用按位或运算符连接起来,所以这些常数的宏定义都以O_开头,表示or。O_RDONLY 只读打开、 O_WRONLY 只写打开、O_RDWR 可读可写打开,这三个常数中必须且仅允许指定一个,其他的可选。

注意open函数与C标准I/O库的fopen函数的区别:
        ① 以可写的方式fopen一个文件时,如果文件不存在会自动创建,而open一个文件时必须明确指定O_CREAT才会创建文件,否则文件不存在就出错返回。
        ② 以w或w+方式fopen一个文件时,如果文件已存在就截断为0字节,而open一个文件时必须明确指定O_TRUNC才会截断文件,否则直接在原来的数据上改写。
        ③ 第三个参数mode指定文件权限,创建新文件才需指定,可以用八进制数表示,比如0644表示-rw-r-r–,也可以用S_IRUSR、S_IWUSR等宏定义按位或起来表示,详见open(2)的Man Page。要注意的是,文件权限由open的mode参数和当前进程的umask掩码共同决定。
close函数
关闭一个已打开的文件, 返回值:成功返回0,出错返回-1并设置errno
#include <unistd.h>
int close(int fd);             参数fd是要关闭的文件描述符。需要说明的是,当一个进程终止时,内核对该进程所有尚未关闭的文件描述符调用close关闭,所以即使用户程序不调用close,在终止时内核也会自动关闭它打开的所有文件。但是对于一个长年累月运行的程序(比如网络服务器),打开的文件描述符一定要记得关闭,否则随着打开的文件越来越多,会占用大量文件描述符和系统资源。
        由open返回的文件描述符一定是该进程尚未使用的最小描述符。由于程序启动时自动打开文件描述符0、1、2,因此第一次调用open打开文件通常会返回描述符3,再调用open就会返回4。可以利用这一点在标准输入、标准输出或标准错误输出上打开一个新文件,实现重定向的功能。例如,首先调用close关闭文件描述符1,然后调用open打开一个常规文件,则一定会返回文件描述符1,这时候标准输出就不再是终端,而是一个常规文件了,再调用printf就不会打印到屏幕上,而是写到这个文件中了。
3、read/write
read函数
从打开的设备或文件中读取数据; 返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调read之前已到达文件末尾,则这次read返回0.
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);          参数count是请求读取的字节数,读上来的数据保存在内核缓冲区buf中,同时文件的当前读
写位置向后移。注意返回值类型是ssize_t,表示有符号的size_t,这样既可以返回正的字节数、0(表示到达文件末尾)也可以返回负值-1(表示出错)。
        读常规文件时,读指定count个字节(遇到文件末尾立即结束)。
        从终端设备读,通常以行为单位,读到换行符就返回了。
        从网络读,根据不同的传输层协议和内核缓存机制,返回值可能小于请求的字节数。
write函数
        向打开的设备或文件中写数据;返回值:成功返回写入的字节数,出错返回-1并设置errno。
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
        写常规文件时,write的返回值通常等于请求写的字节数count,而向终端设备或网络写则不一定。
3、lseek
        lseek和标准I/O库的fseek函数类似,可以移动当前读写位置(或者叫偏移量)。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);          参数offset和whence的含义和fseek函数完全相同。只不过第一个参数换成了文件描述
符。和fseek一样,偏移量允许超过文件末尾,这种情况下对该文件的下一次写操作将延长文件,中间空洞的部分读出来都是0(文件拓展)。
若lseek成功执行,则返回新的偏移量,因此可用以下方法确定一个打开文件的当前偏移量:
off_t currpos;
currpos = lseek(fd, 0, SEEK_CUR);
4、fcntl/ioctl
fcntl函数
fcntl函数改变一个已打开的文件的属性,可以重新设置读、写、追加、非阻塞等标志(这些标志称为File Status Flag),而不必重新open文件。
#include <unistd.h>
#include <fcntl.h>
int fcntl(int fd, int cmd);
int fcntl(int fd, int cmd, long arg);
int fcntl(int fd, int cmd, struct flock *lock);      用fcntl改变File Status Flag:
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#define MSG_TRY "try again\n"
int main(void)
{
        char buf;
        int n;
        int flags;
        flags = fcntl(STDIN_FILENO, F_GETFL);
        flags |= O_NONBLOCK;
if (fcntl(STDIN_FILENO, F_SETFL, flags) == -1) {
        perror("fcntl");
        exit(1);
}
tryagain:
        n = read(STDIN_FILENO, buf, 10);
        if (n < 0) {
                if (errno == EAGAIN) {
                        sleep(1);
                        write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY));
                        goto tryagain;
                }
        perror("read stdin");
        exit(1);
        }
        write(STDOUT_FILENO, buf, n);
        return 0;
}      ioctl函数
ioctl用于向设备发控制和配置命令,有些命令也需要读写一些数据,但这些数据是不能用read/write读写的,称为Out-of-band数据。也就是说,read/write读写的数据是in-band数据,是I/O操作的主体,而ioctl命令传送的是控制信息,其中的数据是辅助的数据。例如,在串口线上收发数据通过read/write操作,而串口的波特率、校验位、停止位通过ioctl设置,A/D转换的结果通过read读取,而A/D转换的精度和工作频率通过ioctl设置。
#include <sys/ioctl.h>
int ioctl(int d, int request, ...);    d是某个设备的文件描述符。request是ioctl的命令,可变参数取决于request,通常是一个指向变量或结构体的指针。若出错则返回-1,若成功则返回其他值,返回值也是取决于request。
以下程序使用TIOCGWINSZ命令获得终端设备的窗口大小。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
int main(void)
{
        struct winsize size;
        if (isatty(STDOUT_FILENO) == 0)
                exit(1);
        if(ioctl(STDOUT_FILENO, TIOCGWINSZ, &size)<0) {
                perror("ioctl TIOCGWINSZ error");
                exit(1);
        }
        printf("%d rows, %d columns\n", size.ws_row, size.ws_col);
        return 0;
}在图形界面的终端里多次改变终端窗口的大小并运行该程序,观察结果。
5、errno、perror
errno:
        全局变量errno   是记录系统的最后一次错误代码。代码是一个int型的值,在errno.h中定义。查看错误代码errno是调试程序的一个重要方法。
① 第 1-34 个错误定义: /usr/include/asm-generic/errno-base.h
    第 35-133 个错误定义:/usr/include/asm-generic/errno.h

② 每个errno值对应着以字符串表示的错误类型; 当调用“某些”函数出错时,该函数会自动重新设置errno值。
perror:
        用来将上一个函数发生错误的原因输出到标准设备stderr; 参数 s 所指的字符串会先打印,后面再加上错误的原因字符串; 参数 s 所指的字符串会先打印,后面再加上错误的原因字符串.
        头文件:   stdio.h
        函数定义:        void perror(const char *s)

页: [1]
查看完整版本: 006-文件I/O