目录
一、Linux系统IO和C标准库IO
1.1 标准C库IO函数
1.2 标准C 库IO和 Linux系统IO的关系
标准C 库IO:带缓冲区
Linux系统IO:不带缓冲区,(有些书上说Linux系统有缓存,那是内核的缓存,不是IO的缓存)
二、 虚拟地址空间
下图以32位机为例,2的32次方为4G,64位机是2的64次方,太大了不好画。
三、文件描述符表和文件描述符
- 文件描述符(file descriptor)通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。
- 每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。
文件描述符表默认大小: 1024,每个进程启动之后, 都有一个文件描述符表,所以每个进程默认能打开的文件个数: 1024
前三个文件文件描述符是默认被使用了的: - 标准输入 -> 0, 标准输出 -> 1, 标准错误 -> 2
除去被占用的每个进程默认能打开的文件个数: 1021
6.1 dup和dup2函数
-
dup 复制文件描述符
#include <unistd.h>
int dup(int oldfd);
例子:
int ret = dup(3);
值==3的文件描述符指向一个文件a.txt,返回值是从空闲的文件描述符表中找到的最小的一个, 这时候 4 也指向a.txt
-
dup2 重定向文件描述符
#include <unistd.h>
int dup2(int oldfd, int newfd);
dup2把newfd和newfd指向的文件都close,然后把newfd指向oldfd所指向的文件。
例如:oldfd指向 a.txt, newfd 指向 b.txt,函数调用成功之后: newfd和b.txt做close, newfd指向了a.txt
oldfd必须是一个有效的文件描述符 , oldfd和newfd是相同的值, 等于什么也没做
-
fcntl
常用用法:
#include <unistd.h>
#include <fcntl.h>
int ret = fcntl(fd, F_DUPFD);
- 参数: F_DUPFD, 复制文件描述符, 复制的是第一个参数: fd, 会得到一个新的文件描述符: 返回值ret (此用法 == dup)
- 参数: F_GETFL -> 获取文件的flags属性
- 参数: F_SETFL -> 设置文件的flags属性
例子:
// 设置/获取文件open时的状态标志 -> flags
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
- 必选项: O_RDONLY, O_WRONLY, or O_RDWR, 这些属性无法修改
- 在open的时候设置了 O_RDONLY, 不能通过fcntl 设置 -> O_RDWR
- 可以通过fcntl修改flags中的可选项
- O_APPEND 数据追加
O_WRONLY | O_APPEND 写追加
- O_NONBLOCK 设置非阻塞
修改文件flags属性测试代码 :
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
// 打开一个文件
int fd = open("1.txt", O_WRONLY);
if(fd == -1)
{
perror("open");
return -1;
}
// 追对文件的操作属性,先得到原来设置的属性
int flag = fcntl(fd, F_GETFL);
// 属性添加
flag |= O_APPEND;
// 设置属性到文件描述符中
fcntl(fd, F_SETFL, flag);
// 写文件
char* p = "你好, 世界....";
write(fd, p, strlen(p));
close(fd);
return 0;
}
四、Linux系统IO函数
- errno -> 属于Linux系统函数库, 库里边的一个全局变量, 记录的是错误号
#include <stdio.h>
void perror(const char *s); - s参数: 用户描述, 比如:hello
perror打印的是errno对应的错误描述, 实际输出: hello: xxxxx(实际的错误描述)
- open
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// open函数
// 打开一个已经存在文件
int open(const char *pathname, int flags);
参数:
- pathname: 要打开的文件路径
- flags: 对文件的操作权限设置
- O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的
返回值:
代码打开一个文件意味着获得了这个文件的访问句柄
// 使用open创建一个新文件
int open(const char *pathname, int flags, mode_t mode);
参数:
- pathname: 要打开的文件路径
- flags: 对文件的操作权限设置
- 必选项: O_RDONLY, O_WRONLY, O_RDWR 这三个设置是互斥的
- 可选项: O_CREAT -> 文件不存在, 创建新文件
- mode: 八进制的数, 表示用户对创建出的新文件的操作权限, 比如: 0775, mode & ~umask
0777 -> 111111111
0775 -> 111111101
&
0775 111111101
按位与: 0和任何数都为0
- close
#include <unistd.h>
int close(int fd);
- read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:
- fd: open得到的, 通过这个fd操作某一个文件
- buf: 缓冲区, 存储读到的数据, 数组的地址
- count: buf的大小
返回值:
- 成功:
>0: 返回实际读到的字节数
==0: 文件已经读完了
- 失败: -1
- write
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
参数:
- fd: open得到的, 通过这个fd操作某一个文件
- buf: 要往磁盘写入的数据
- count: 要写的数据的实际大小
返回值:
成功: 实际写入的字节数
失败: -1
- lseek
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
参数:
- fd: open得到的, 通过这个fd操作某一个文件
- offset: 偏移量
- whence:
SEEK_SET
设置文件指针的偏移量
SEEK_CUR
设置偏移量: 当前位置 + 第二个参数的值
SEEK_END
设置偏移量: 文件大小 + 第二个参数的值
int fseek(FILE *stream, long offset, int whence);
// 1. 移动文件指针到文件头
lseek(fd, 0, SEEK_SET);
// 2. 获取当前文件指针的位置
lseek(fd, 0, SEEK_CUR);
// 3. 获取文件长度
lseek(fd, 0, SEEK_END);
// 4. 拓展文件长度, 当前文件10b, 110b, 增加的字节的0
lseek(fd, 100, SEEK_END);
// 要进行一次写操作
write(fd, " ", 1);
五、Linux其他系统函数
- stat / lstat 根据文件名字,获取文件对应属性。
struct stat {
dev_t st_dev; //文件的设备编号
ino_t st_ino; //节点
mode_t st_mode; //文件的类型和存取的权限
nlink_t st_nlink; //连到该文件的硬连接数目,刚建立的文件值为1
uid_t st_uid; //用户ID
gid_t st_gid; //组ID
dev_t st_rdev; //(设备类型)若此文件为设备文件,则为其设备编号
off_t st_size; //文件字节数(文件大小)
blksize_t st_blksize; //块大小(文件系统的I/O 缓冲区大小)
blkcnt_t st_blocks; //块数
time_t st_atime; //最后一次访问时间
time_t st_mtime; //最后一次修改时间
time_t st_ctime; //最后一次改变时间(指属性)
};
关于变量 st_mode:
- st_mode -- 16位整数
○ 0-2 bit -- 其他人权限
- S_IROTH 00004 读权限
- S_IWOTH 00002 写权限
- S_IXOTH 00001 执行权限
- S_IRWXO 00007 掩码, 过滤 st_mode中除其他人权限以外的信息
○ 3-5 bit -- 所属组权限
- S_IRGRP 00040 读权限
- S_IWGRP 00020 写权限
- S_IXGRP 00010 执行权限
- S_IRWXG 00070 掩码, 过滤 st_mode中除所属组权限以外的信息
○ 6-8 bit -- 文件所有者权限
- S_IRUSR 00400 读权限
- S_IWUSR 00200 写权限
- S_IXUSR 00100 执行权限
- S_IRWXU 00700 掩码, 过滤 st_mode中除文件所有者权限以外的信息
○ 12-15 bit -- 文件类型
- S_IFSOCK 0140000 套接字
- S_IFLNK 0120000 符号链接(软链接)
- S_IFREG 0100000 普通文件
- S_IFBLK 0060000 块设备
- S_IFDIR 0040000 目录
- S_IFCHR 0020000 字符设备
- S_IFIFO 0010000 管道
- S_IFMT 0170000 掩码,过滤 st_mode中除文件类型以外的信息
(st_mode & S_IFMT) == S_IFREG
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
函数说明:
int stat(const char *pathname, struct stat *buf);
参数:
- pathname: 操作的文件的路径
- buf: 结构体变量, 传出
例子:
struct stat st;
stat("hello.txt", &st);
stat是一个int类型数据,一个int数据可以表示32位2进制数,具体如下:
- opendir 目录遍历函数
DIR *opendir(const char *name);
参数:
- name: 要打开的目录
返回值: DIR *
struct dirent
{
ino_t d_ino; // 此目录进入点的inode
ff_t d_off; // 目录文件开头至此目录进入点的位移
signed short int d_reclen; // d_name 的长度, 不包含NULL 字符
unsigned char d_type; // d_name 所指的文件类型
har d_name[256]; // 文件名
};
d_type
DT_BLK - 块设备
DT_CHR - 字符设备
DT_DIR - 目录
DT_LNK - 软连接
DT_FIFO - 管道
DT_REG - 普通文件
DT_SOCK - 套接字
DT_UNKNOWN - 未知
struct dirent *readdir(DIR *dirp);
- 返回一个结构体, 这个对应一个文件
int closedir(DIR *dirp);