系统调用、文件
一、系统调用(API)
系统调用就是操作系统提供的一些功能给程序员使用,这些功能已经被封装成C函数的形式,但它们不是标准C的一部分
一般应用程序运行在用户态(使用的是0 ~ 3G的虚拟内存),系统调用时工作在内核态(使用的是3~4G的虚拟内存)
常用的标准库函数绝大部分时间运行在用户态,底层偶尔也会调用系统调用进入内核态
系统调用时运行的就是内核代码,内核代码属性内核的一部分,该代码的外部接口以函数形式定义在共享库中(linux-gate.so ld-linux.so.2)
time ./a.out
real 0m0.012s 执行总用时
user 0m0.000s 用户态执行时间
sys 0m0.000s 内核态执行时间
二、一切皆文件
UNIX/Linux 为操作方便把所有的服务、设备等内容都抽象成了文件的形式,并提供了一套简单而统一的接口,这部分接口称为系统文件IO
也就是说UNIX/Linux把任何对象都可以当做文件处理,以文件形式访问
文件分类:
普通文件 - 包括纯文本文件、二进制文件、
目录文件 d 类似Windows文件夹(必须有执行权限才能进入)
块设备文件 b 保存大块数据的设备,例如硬盘
字符设备文件 c 例如键盘、鼠标等等
链接文件 l 类似Windows的快捷方式
管道文件 p
Socket文件 s 通常用于网络数据链接
三、文件系统的调用
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
/*
功能:打开文件
pathname:文件的路径
flags:打开文件的方式
O_RDONLY 只读
O_WRONLY 只写
O_RDWR 读写
O_APPEND 追加,文件位置指针在末尾
O_CREAT 文件不存在则创建
O_EXCL 文件存在则创建失败
O_TRUNC 如果文件存在则清空
返回值:文件描述符,类似于标准库中的FILE*,代表了一个打开的文件,成功返回非负数,失败返回-1
*/
int open(const char *pathname, int flags, mode_t mode);
/*
功能:创建文件
pathname:文件的路径
flags:打开文件的方式
只能写O_CREAT
mode:权限掩码
S_IRWXU 00700 拥有者 读写执行权限
S_IRUSR 00400 拥有者 读权限
S_IWUSR 00200 拥有者 写权限
S_IXUSR 00100 拥有者 执行权限
S_IRWXG 00070 同组 读写执行权限
S_IRGRP 00040 同组 读权限
S_IWGRP 00020 同组 写权限
S_IXGRP 00010 同组 执行权限
S_IRWXO 00007 其他 读写执行权限
S_IROTH 00004 其他 读权限
S_IWOTH 00002 其他 写权限
S_IXOTH 00001 其他 执行权限
*/
int creat(const char *pathname, mode_t mode);
/*
功能:创建文件
mode:同open的mode
返回值:文件描述符
*/
r O_RDONLY
r+ O_RDWR
w O_WRONLY|O_CREAT|O_TRUNC, 0666
w+ O_RDWR|O_CREAT|O_TRUNC, 0666
a O_WRONLY|O_CREAT|O_APPEND, 0666
a+ O_RDWR|O_CREAT|O_APPEND, 0666
#include <unistd.h>
ssize_t write(int fd, const void *buf, size_t count);
/*
功能:把内存中的数据写入到文件中
fd:文件描述符,也就是open的返回值
buf:待写入的内存首地址
count:要写入的字节数
返回值:成功写入的字节数
*/
ssize_t read(int fd, void *buf, size_t count);
/*
功能;从文件中读取数据到内存中
fd:文件描述符
buf:数据存储的内存首地址
count:想要读取的字节数
返回值:实际读取到的字节数
*/
int close(int fd);
/*
功能:关闭文件
返回值:成功返回0,失败返回-1
*/
直接使用标准IO比系统IO写入的速度更快,原因是标准IO有缓冲区机制,在写入数据时并不是直接调用系统IO,而是先把缓冲区填满,然后再调用系统IO把缓冲区中的数据一并写入到文件中,提高读写速度。
直接使用系统IO会频繁地切换用户态和内核态,更加耗时。
如果我们也给系统IO增加一个缓存区机制时,它的速度就会比标准IO快
标准IO > 系统IO
系统IO + 缓冲区 > 标准IO
四、随机读写
每个打开的文件都有一个记录着读写位置的文件位置指针,对文件的读写操作都从该指针指向的位置进行,位置指针会随着读写操作而增加移动。
一个打开的文件,位置指针指向开头,如果使用O_APPEND,则在文件末尾
如果想要随意读取文件中的任意位置的数据,可以通过调整位置指针来实现
//标准IO
#include<stdio>
int fseek(FILE *stream, long offset, int whence);
/*
返回值:成功0 失败-1
*/
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
/*
fd:文件描述符
offset:偏移量
whence:基础位置
SEEK_SET 文件开头
SEEK_CUR 当前位置
SEEK_END 文件末尾
返回值:调整后文件位置指针所在的位置
如果在越过文件末尾的位置写入数据,将在中间形成空洞,空洞会被计算入文件大小中,但是不占用磁盘空间
*/
五、系统IO读写文本文件
系统IO是没有fprintf/fscanf函数的,因此不能直接读写文本文件
写文本文件:
对象 sprintf 转换成字符串,然后在通过write写入文件
read字符串形式读取,然后再使用sscanf解析转换成对应的数据使用
六、文件描述符
1、非负整数,代表一个打开的文件
2、通过系统调用(open\creat)返回,也可以被内核空间引用
3、它代表了一个内核对象(类似于FILE对象),因为内核不能暴露该对象的地址,因此通过它来表示
4、内核中有一张表记录了所有打开的文件对象,文件描述符就是访问这张表的下标,相当于访问文件对象的凭证
内核中有三个默认打开的文件描述符
0 标准输入 stdin
1 标准输出 stdout
2 标准错误 stderr
文件描述符的复制
#include <unistd.h>
int dup(int oldfd);
/*
功能:复制一个已经打开的文件描述符
返回值:返回一个没有用过的最小的文件描述符
*/
int dup2(int oldfd, int newfd);
/*
功能:复制成一个指定的文件描述符
newfd:想要复制成的文件描述符,如果newfd已经打开,会先关闭,再复制
返回值:返回newfd的值
*/
//注意:复制成功后,相当于两个不同的文件描述符对应同一个打开的文件
七、文件同步
1、在写入数据时内存与磁盘之间也有一个缓冲区,这种机制降低了磁盘的读写次数,提高了读写的效率
2、这种机制带来的后果就是磁盘中的数据与实际写入的数据不匹配,系统提供了三个函数可以让缓冲区中的数据立即写入到磁盘上
#include <unistd.h>
void sync(void);
/*
功能:把缓冲区中的数据立即同步到磁盘上
注意:并不会等待数据全部同步完,而是把缓冲区的数据加入写入队列后,立即返回
*/
int fsync(int fd);
/*
功能:把指定文件的内容从缓冲区同步到磁盘上
注意:会等到全部写入到磁盘后才会返回
返回值:0成功 ,-1失败
*/
int fdatasync(int fd);
/*
功能:把指定文件的内容从缓冲区同步到磁盘上,只同步文件的内容不同步属性
注意:会等到全部写入到磁盘后才返回
*/
八、文件属性
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int stat(const char *path, struct stat *buf);
/*
功能:根据文件的路径获取文件的属性
buf:存储文件属性的结构体指针,是一个输出型参数
*/
int fstat(int fd, struct stat *buf);
/*
功能:根据文件描述符获取文件的属性
*/
int lstat(const char *path, struct stat *buf);
/*
功能:获取软链接文件的文件属性
*/
struct stat {
dev_t st_dev; // 设备IO
ino_t st_ino; // inode 编号
mode_t st_mode; // 文件的类型和权限
nlink_t st_nlink; // 硬链接数
uid_t st_uid; // 用户ID
gid_t st_gid; // 组ID
dev_t st_rdev; // 特殊设备ID号
off_t st_size; // 总字节数
blksize_t st_blksize; // IO块字节数
blkcnt_t st_blocks; // 占用512字节块数
time_t st_atime; // 最后访问时间
time_t st_mtime; // 最后内容修改时间
time_t st_ctime; // 最后状态修改时间
};
/*
&操作
在POSIX中,有对于st_mode 有以下宏可以判断文件属于哪个类型:
S_ISREG(m) 检查是否是普通文件
S_ISDIR(m) 检查是否是目录文件
S_ISCHR(m) 检查是否是字符设备文件
S_ISBLK(m) 检查是否是块设备文件
S_ISFIFO(m) 检查是否是管道文件
S_ISLNK(m) 检查是否是软链接文件
S_ISSOCK(m) 检查是否是Socket文件
使用st_mode 与下面掩码进行处理,能够获取文件的类型和权限:
S_IFMT 0170000 获取文件类型的掩码
S_IFSOCK 0140000 socket文件
S_IFLNK 0120000 软链接文件
S_IFREG 0100000 普通文件
S_IFBLK 0060000 块设备文件
S_IFDIR 0040000 目录文件
S_IFCHR 0020000 字符设备文件
S_IFIFO 0010000 管道文件
S_ISUID 0004000 设置用户ID
S_ISGID 0002000 设置组ID
S_ISVTX 0001000 sticky bit (see below)
S_IRWXU 00700 用户权限掩码
S_IRUSR 00400 用户读权限
S_IWUSR 00200 用户写权限
S_IXUSR 00100 用户执行权限
S_IRWXG 00070 组权限掩码
S_IRGRP 00040 读
S_IWGRP 00020 写
S_IXGRP 00010 执行
S_IRWXO 00007 其他用户掩码
S_IROTH 00004 读
S_IWOTH 00002 写
S_IXOTH 00001 执行
*/
九、文件权限
#include <unistd.h>
int access(const char *pathname, int mode);
/*
功能:测试当前用户对文件有哪些权限
pathname:文件路径
mode:想要测试的权限
F_OK 文件是否存在
R_OK 写权限
W_OK 读权限
X_OK 执行权限
返回值:存在则返回0,不存在返回-1
注意:如果文件不存在测试RWX权限也会返回-1
*/
#include <sys/stat.h>
int chmod(const char *path, mode_t mode);
/*
功能:根据文件路径修改文件权限
path:文件路径
mode:由三位的八进制数组成的权限码
0644 普通文件
0755
返回值:成功返回0,失败返回-1
*/
int fchmod(int fd, mode_t mode);
/*
功能:根据文件描述符修改文件权限
*/
/*
权限屏蔽码:
如果我们不让新创建的文件具有某些权限,可以通过设置权限屏蔽码进行过滤
通过命令 umask 查看当前终端的权限屏蔽码
通过命令 umask 0xxx 可以修改当前终端的权限屏蔽码
注意:权限屏蔽码对于chmod/fchmod函数和命令无效
*/
#include <sys/types.h>
#include <sys/stat.h>
mode_t umask(mode_t mask);
/*
功能:设置当前进程的权限屏蔽码
mask:想要设置的权限屏蔽码
返回值:返回旧的权限屏蔽码
注意:该函数的设置只对当前进程有效,进程结束后就失效了
*/
十、修改文件大小
#include <unistd.h>
#include <sys/types.h>
int truncate(const char *path, off_t length);
/*
功能:根据文件路径修改文件的大小
length:想要修改成的字节数大小
*/
int ftruncate(int fd, off_t length);
/*
功能:根据文件描述符修改文件大小
length:想要成的字节数大小
*/
十一、删除和重命名
#include <stdio.h>
int remove(const char *pathname);
/*
功能:标准库中的删除文件函数,底层调用unlink
pathname:待删除的文件路径
*/
#include <unistd.h>
int unlink(const char *pathname);
/*
功能:删除文件
*/
int rename(const char *oldpath, const char *newpath);
/*
功能:重命名文件
*/
十二、链接文件
Linux的文件系统
inode 信息块:默认128b 记录了文件的权限、大小、所有者、修改时间等信息,以及对应的block的位置信息。
block 块:默认4k 记录了文件名和真正的数据信息
每个文件必须拥有唯一一个inode以及若干个block,读取文件时,需要借助目录的block中记录的文件名找到该文件的inode号,通过inode读取到该文件的block
什么软硬链接文件?
硬链接:硬链接文件没有自己的inode和block,只是在不同目录下复制了一份源文件的inode信息,可以通过inode访问到源文件的同一份block
软链接:软链接文件会建立自己的新的inode和block,而软链接文件的block中存储的是源文件的文件名和inode号
区别
1、如果修改硬链接文件内容,源文件以及其他的硬链接文件也会被修改,而软链接不会
2、当删除源文件,只是删除了源文件的inode,硬链接文件不受影响,但是软链接文件无法访问源文件
3、当硬链接数被删除为0时,文件才算是被真正的删除了
4、软链接可以链接目录,硬链接不可以
5、软链接可以跨文件系统,硬链接不行
#include <unistd.h>
int link(const char *oldpath, const char *newpath);
/*
功能:创建硬链接文件
*/
int symlink(const char *oldpath, const char *newpath);
/*
功能:创建软链接文件
*/
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
/*
功能:读取软链接文件数据,而非链接目标数据,读取的其实是链接的路径
buf:是一个输出型参数,获取链接路径
bufsize:获取的字节数
*/
十三、目录操作
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname, mode_t mode);
/*
功能:创建目录
mode:目录的权限,注意必须要有执行权限,否则无法进入
*/
#include <unistd.h>
int rmdir(const char *pathname);
/*
功能:删除空目录
*/
int chdir(const char *path);
/*
功能:进入某个目录,相当于cd命令
*/
char *getcwd(char *buf, size_t size);
/*
功能:获取当前工作目录,相当于pwd命令
*/
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
/*
功能:打开目录文件,返回一个目录流
name:选择要打开的目录
*/
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
/*
功能:从目录流中读取一条记录
struct dirent {
ino_t d_ino; // i节点号
off_t d_off; // 下一条条目的偏移量
unsigned short d_reclen; // 当前条目的长度
unsigned char d_type; // 文件类型
DT_BLK 块设备文件
DT_CHR 字符设备文件
DT_DIR 目录文件
DT_FIFO 管道文件
DT_LNK 链接文件
DT_REG 普通文件
DT_SOCK socket文件
DT_UNKNOWN 未知类型
char d_name[256]; // 文件名
};
*/
void seekdir(DIR *dirp, long loc);
/*
功能:设置目录流的位置指针,用于随机读取
*/
long telldir(DIR *dirp);
/*
功能:设置当前目录流的位置指针位置
*/
#include <sys/types.h>
#include <dirent.h>
void rewinddir(DIR *dirp);
/*
功能:设置目录流的位置指针到开头
*/