open() close() read() write() lseek()等系统调用函数的使用,在Linux系统调用函数里都是使用文件描述符来操作文件,只有在用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);
pathname:文件名
flags:
必选项:O_RDONLY(只读) O_WRONLY(只写) O_RDWR(读写)
可选项:O_APPEND(追加) O_TRUNC(截断) O_CREAT(创建文件) O_NONBLOCK(非阻塞)
当O_EXCL 与 O_CREAT一起使用的时候,要保证一定创建了一个新文件,如果文件已存在,则会报错
需要注意的是flags的本质是32位整数,即每一个标志比如O_RDONLY都是让某一位置位,因此如果我们要设置打开文件的很多属性的时候,中间要用按位或 “|” 来连接。
mode:权限位,mode&~umask是打开文件最后的真正权限,umask表示要减掉的权限,在进程控制块中也有umask成员。比如umask是0002的时候,表示要丢掉其他用户others的可写权限。
返回值:用open打开一个文件后,内核会在内核去创建一个FILE结构体加入打开文件链表,然后进程控制块中文件描述符表中的最小可用文件描述符会分配给这个FILE结构体,然后open函数返回这个文件描述符fd。
#include <unistd.h> int close(int fd);
fd:文件描述符
关闭文件,成功返回0失败返回-1并设置errno。
#include <unistd.h> ssize_t read(int fd, void *buf, size_t count);
fd:要读的文件对应文件描述符
buf:把文件中的内容读到buf缓冲区中
count:缓冲区大小,意味着计划读取内容的大小
返回值:
成功,返回读到的大小
失败返回-1,设置errno
0,代表已经读到了文件末尾
#include <unistd.h> ssize_t write(int fd, const void *buf, size_t count);
fd:文件描述符
buf:把buf缓冲区中的内容写到fd文件中
count:缓冲区大小
返回值:
成功,返回写入的字节数
失败,返回-1,设置errno
0,代表未写入
用于改变文件读写位置,read和write函数的使用都是会改变文件的读写位置的,读写位置其实就是FILE结构体中的f_pos成员
#include <sys/types.h> #include <unistd.h> off_t lseek(int fd, off_t offset, int whence);
fd:文件描述符
offset:偏移量,一般设置成0就行
whence:
SEEK_SET:文件开始位置
SEEK_CUR:当前位置
SEEK_END:结尾
返回值:
成功,返回当前位置到文件开始的长度
失败,返回-1,设置errno
lseek有衍生出三个功能
移动文件读写位置
计算文件大小
拓展文件
问题描述:打开一个文件,写入内容,然后将内容输出到屏幕
第一版代码
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> int main(int argc, char *argv[]){ int fd = open(argv[1], O_RDWR|O_CREAT, 0666); write(fd, "hello world", 11); // int fd1 = open(argv[1], O_RDWR|O_CREAT, 0666); char buf[256] = {0}; int ret = read(fd, buf, sizeof(buf)); if(ret){ write(STDOUT_FILENO, buf, ret); } close(fd); // close(fd1); return 0; }
由于write函数写入数据之后,数据的读写位置变到了文件末尾,因此这一版代码是没有输出的,因为read调用读数据的时候f_pos已经到了文件末尾了,所以read啥都没读到。因为每个FILE结构体对应一个f_pos读写位置,所以一个很简单的做法是用open重新打开一个文件,这样内核会对同一个文件再创建一个FILE结构体,而这个新的结构体的读写位置是在开头的。
也可以用lseek直接改变文件读写位置
第二版代码
lseek(fd, 0, SEEK_SET); 把文件描述符fd对应文件的读写位置改到开头。注意我们第二个参数偏移量用的0,如果用3的话,那么就会把SEEK_SET对应的位置再偏移三个字节当成开头位置,即第三个位置开始读了。
#include <stdio.h> #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #include <fcntl.h> int main(int argc, char *argv[]){ int fd = open(argv[1], O_RDWR|O_CREAT, 0666); write(fd, "hello world", 11); lseek(fd, 0, SEEK_SET); // int fd1 = open(argv[1], O_RDWR|O_CREAT, 0666); char buf[256] = {0}; int ret = read(fd, buf, sizeof(buf)); if(ret){ write(STDOUT_FILENO, buf, ret); } close(fd); // close(fd1); return 0; }
其实也是移动文件读写位置的副产品,把文件读写位置移动到末尾,返回的值就是文件的大小。当然偏置量要置零。
int ret = lseek(fd, 0, SEEK_END);
还是移动文件读写位置的副产品,从SEEK_END开始,把偏移量设置成1024,即把读写位置改到文件末尾的后边第1024个位置,然后往文件里写东西,就能顺利拓展文件大小,如果不及时写东西的话是没办法保存这种设置的。就是一种生米煮成熟饭的思想,已经在第1025个位置写东西了,那中间的位置也都属于当前文件了。
一句话,阻塞与非阻塞不是函数的性质,也不是磁盘文件的性质,而是打开文件的性质,即当我们用open打开一个文件的时候,内核会创建一个FILE结构体,这个FILE结构体对应了一个文件描述符fd,通过fd操作文件的时候阻塞还是非阻塞是由FILE结构体对象中的flags成员控制的。
比如read函数,因为Linux中一切皆文件,read在读取设备或管道或套接字文件等的时候,可能会发生阻塞。在open函数打开文件的时候,flags中也有一个选项是设置非阻塞的。内核创建的FILE结构体对象中有一个成员即对应了open时候指向的flags。
第一版代码:
打开的文件 "/dev/tty"是终端,这个时候如果终端没有输入,则read函数是一直阻塞等待的,阻塞等待的性质不是read函数的,而是对应的打开文件的,即用open打开文件的时候默认是以阻塞的方式打开,但是也可以设置该打开文件(对应内核中一个FILE结构体)是非阻塞的。
第二版代码:
用open打开终端文件的时候指定以非阻塞的方式打开,则如果终端没有输入会直接跳过
#include <unistd.h> #include <fcntl.h> int fcntl(int fd, int cmd, ... /* arg */ );
fd:指定文件描述符
cmd:一个宏,对应了具体要用fcntl实现的功能
arg:宏对应的一些参数
用fcntl可以改变文件描述符对应FILE的阻塞性质
F_GETFL指定fcntl要实现的功能,即返回fd对应FILE的flags
然后对flags进行重新设置
再用F_SETFL来把新的flags设置回去
Linux虚拟文件系统(VFS)_chk_plusplus的博客-CSDN博客
由Linux虚拟文件系统的知识我们知道,在磁盘上每个文件都会对应一个唯一的inode,在VFS中也有inode的缓存。inode中存放了除了文件名以外的文件所有的元数据。
#include <sys/types.h> #include <sys/stat.h> #include <unistd.h> int stat(const char *pathname, struct stat *statbuf); int fstat(int fd, struct stat *statbuf); int lstat(const char *pathname, struct stat *statbuf);
pathname:文件名
statbuf:用于存放stat结构体对象的一块内存区域
成功返回0,失败返回-1
stat结构体是用户区用于存放inode的对象的结构体,与VFS中的inode结构体相对应,具体定义如下:
struct stat { dev_t st_dev; /* ID of device containing file -文件所在设备的ID*/ ino_t st_ino; /* inode number -inode节点号*/ mode_t st_mode; /* protection -保护模式?*/ nlink_t st_nlink; /* number of hard links -链向此文件的连接数(硬连接)*/ uid_t st_uid; /* user ID of owner -user id*/ gid_t st_gid; /* group ID of owner - group id*/ dev_t st_rdev; /* device ID (if special file) -设备号,针对设备文件*/ off_t st_size; /* total size, in bytes -文件大小,字节为单位*/ blksize_t st_blksize; /* blocksize for filesystem I/O -系统块的大小*/ blkcnt_t st_blocks; /* number of blocks allocated -文件所占块数*/ time_t st_atime; /* time of last access -最近存取时间*/ time_t st_mtime; /* time of last modification -最近修改时间*/ time_t st_ctime; /* time of last status change - */ };
linux还提供一个stat命令,可以直接连文件名都显示出来
stat遇到软链接会穿透!获取到的是原文件对应的inode信息,而lstat直接获取到的是软链接本身的inode信息。
#include <unistd.h> int access(const char *pathname, int mode);
获取当前用户对应的文件权限
pathname:文件路径
mode:R_OK(可读) W_OK(可写) X_OK(可执行) F_OK(是否存在)
返回值:若存在此权限则返回0,否则返回-1
#include <sys/stat.h> int chmod(const char *pathname, mode_t mode); int fchmod(int fd, mode_t mode);
成功返回0,失败返回-1
#include <unistd.h> int chown(const char *pathname, uid_t owner, gid_t group); int fchown(int fd, uid_t owner, gid_t group); int lchown(const char *pathname, uid_t owner, gid_t group);
pathname:文件名
owner:用户ID
group:组ID
#include <stdio.h> int rename(const char *oldpath, const char *newpath);
重命名文件
#include <unistd.h> #include <sys/types.h> int truncate(const char *path, off_t length); int ftruncate(int fd, off_t length);
path:文件名,对应的文件必须存在
length:如果大于原文件长度,则将原文件拓展,否则将原文件截断为length长度
在命令行中用ln创建硬链接,ln -s创建软链接,但是在系统调用中软硬链接创建用的是不同的函数。
#include <unistd.h> int link(const char *oldpath, const char *newpath);
oldpath:原文件
newpath:硬链接文件
#include <unistd.h> int symlink(const char *target, const char *linkpath);
target:原文件
linkpath:软链接文件
用来读取软链接本身的内容
#include <unistd.h> ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
pathname:文件名
buf:用来存放软链接内容的缓冲区
bufsiz:缓冲区大小
删除软硬链接
#include <unistd.h> int unlink(const char *pathname);
成功返回0,失败返回-1
如果先用open打开了文件,然后再用unlink删除,则会等进程结束之后再删除文件。
#include <unistd.h> char *getcwd(char *buf, size_t size);
获取当前工作目录
buf:传出参数,保存当前路径
size:缓冲区大小
返回值:成功返回路径的指针,失败返回NULL
#include <unistd.h> int chdir(const char *path); int fchdir(int fd);
改变当前进程工作目录
#include <sys/stat.h> #include <sys/types.h> int mkdir(const char *pathname, mode_t mode);
mode&~umask一起构成了新建文件的权限
#include <unistd.h> int rmdir(const char *pathname);
只能删除空目录,这是真的没啥用
#include <sys/types.h> #include <dirent.h> DIR *opendir(const char *name); DIR *fdopendir(int fd);
DIR不用管,就是用来读取目录内容的一个结构体,不用管DIR具体定义
#include <dirent.h> struct dirent *readdir(DIR *dirp);
读取目录的内容,返回值dirent*,这个得关注内容
#include <sys/types.h> #include <dirent.h> int closedir(DIR *dirp);
文件描述符重定向,通过这两个函数可以实现两个文件描述符指向系统打开文件表中同一表项
#include <unistd.h> int dup(int oldfd); int dup2(int oldfd, int newfd);
将文件描述符表中最小可用描述符指向oldfd指向得文件
先将newfd文件描述符关闭,再将其指向oldfd指向的文件