文件系统概述
Linux内核的各种真实文件系统、块设备和字符设备统一在虚拟文件系统的框架中,虚拟文件系统为应用提供了一组抽象的文件输入输出接口。
虚拟文件系统是对各种真实文件系统的抽象,在虚拟文件系统中定义了抽象的超级块、i节点和目录,它为真实文件系统提供了一种统一的框架接口。真实文件系统通过这些接口与虚拟文件系统相连接,真实文件系统是这些抽象接口的具体实现。
虚拟文件系统存在于内存中,在系统启动时产生,随着系统关闭而消失。
文件操作常用的头文件
#include <unistd.h> //多种必要的POSIX函数与常量
#include <fcntl.h> //文件打开、创建、加锁等操作
#include <sys/stat.h>//文件信息(stat (Unix)等)
#include <sys/types.h>//不同的数据类型
#include <dirent.h> //打开与列出目录内容
//此外,还有C标注库中的
#include <stdio.h> //标准缓存输入输出
文件基本输入输出
文件输入输出涉及到以下函数:
- open、creat 在
<fcntl.h>
中- read、write、lseek、close 在
<unistd.h>
中
案例:复制文件
//cp.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#define PMODE 0644 //权限定义为rw-r--r--
#define BUFSIZE 200
int main(int argc,char *argv[])
{
int fdin,fdout,n;
char buf[BUFSIZ];
if(argc !=3)
{
fprintf(stderr,"Usage:%s filein fileout\n",argv[0]);
return 1;
}
if((fdin = open(argv[1],O_RDONLY)) == -1)
{
perror(argv[1]);
return 2;
}
if((fdout = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,PMODE)) == -1)
{
perror(argv[2]);
return 3;
}
while((n = read(fdin,buf,BUFSIZE)) > 0)
write(fdout,buf,n);
close(fdin);
close(fdout);
return 0;
}
###文件属性操作 文件的属性信息存放在文件对应的i节点中,对于不同类型的物理文件系统,文件属性的组织形式不尽相同,为了获得统一的文件属性格式,Linux定义了struct stat这个数据结构,类型定义如下:
struct stat {
dev_t st_dev; /* 文件设备编号*/
ino_t st_ino; /* i节点号 */
mode_t st_mode; /* 文件类型和存储权限 */
nlink_t st_nlink; /* 硬链接 */
uid_t st_uid; /* 用户ID */
gid_t st_gid; /* 用户组ID */
dev_t st_rdev; /* Device ID (if special file)*/
off_t st_size; /* 文件字节数bytes */
blksize_t st_blksize; /* 块大小 */
blkcnt_t st_blocks; /* 以512bytes为单位的块数 */
struct timespec st_atim; /* 文件最后一次访问时间 */
struct timespec st_mtim; /* 文件最后一次修改时间 */
struct timespec st_ctim; /* 文件属性最后一次改变时间 */
};
以之前的cp.c为例:
可以看到cp.c文件实际大小640bytes,在磁盘上占用了一个4096bytes的块,也就是8个512bytes的块。`
文件属性操作常用函数:
- stat 获取文件属性信息
- chmod 设置文件权限
- chown 设置文件属主
- utime 获取时间
案例:改变文件读写权限
#include <stdio.h>
#include <sys/stat.h>
int main()
{
mode_t fdmode = (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH);
if(chmod("cp.c",fdmode) == -1)
{
printf("error\n");
return 1;
}
return 0;
}
编译运行,结果如下,可见文件权限已经被修改。
niuhe@niuhe-ubuntu:~/Linux$ gcc -o chmod chmod.c
niuhe@niuhe-ubuntu:~/Linux$ ls
chmod chmod.c cp cp.c
niuhe@niuhe-ubuntu:~/Linux$ ll cp.c
-rw-rw-r-- 1 niuhe niuhe 640 10月 6 16:55 cp.c
niuhe@niuhe-ubuntu:~/Linux$ ./chmod
niuhe@niuhe-ubuntu:~/Linux$ ll cp.c
-rw-r--r-- 1 niuhe niuhe 640 10月 6 16:55 cp.c
###目录操作 目录是一种特殊的文件,其内容由若干目录项组成,一个目录项包括文件名和i节点号。为了便于管理,每个目录中都包含当前目录".“和父目录”..",当前目录项指向当前目录的i节点编号,父目录项记录了父目录对应的i节点编号。 常用库函数及头文件如下:
#include <sys/stat.h>
//在某目录中创建一个目录项,分配一个i节点和目录项相链接
//分配一个逻辑块用来存放目录内容,并在其中建立当前目录和父目录两个目录项
int mkdir(const char *pathname,mode_t mode);
#include <unistd.h>
//删除空目录
int rmdir(const char *pathname);
//改变工作目录;在每个进程的进程控制块中保存着当前工作目录的i节点
//初始工作目录继承自父进程,进程运行过程可以改变工作目录
int chdir(const char *pathname);
//获得调用者进程的当前工作目录,buf存放路径,size路径包含字节数
char *getcwd(char *buf, size_t size);
#include <dirent.h>
//打开目录,成功返回目录流(字符串)
DIR *opendir(const char *pahtname);
//读目录,成功返回下一个目录项
struct dirent *readdir(DIR *dp);
//关闭目录
int closedir(DIR *dp);
目录是一种特殊的文件,存放着很多目录项,每一个目录项是一个结构体。
struct dirent
{
long d_ino; //i节点号
char d_name[NAME_MAX+1]; //文件名
off_t d_off; //在目录文件中偏移量
unsigned short d_reclen; //文件名长度
}
案例:改变当前进程工作目录
#include <unistd.h>
#include <stdio.h>
int main(void)
{
if(chdir("/tmp") < 0)
printf("chdir failed");
printf("chdir to /tmp succeeded\n");
return 0;
}
案例:浏览目录中所有文件名
#include <stdio.h>
#include <dirent.h>
int main(int argc,char *argv[])
{
DIR *dirp;
struct dirent *direntp;
if((dirp = opendir(argv[1])) == NULL)
{
printf("cannot open the %s directory",argv[1]);
return 1;
}
while((direntp = readdir(dirp)) != NULL)
printf("%s\n",direntp->d_name);
closedir(dirp);
return 0;
}
###C标准I/O库
运用read、write等系统底层函数进行输入输出时,需要在用户态和内核态之间来回切换。若每次读取或写入数据较少,将导致频繁的I/O操作,降低了程序运行效率。 标准I/O库对底层I/O系统调用进行了封装,提供了带格式转换的I/O操作,并在用户空间增加了缓冲管理,可减少程序和输出设备之间I/O次数。函数原型定义在<stdio.h>中。 细节略
I/O重定向
文件描述符
Linux系统中,进程拥有各自打开文件的描述符。文件描述符按生成的顺序存放在文件描述符表中,Linux内核将文件描述符表用一维数组表述,每个打开的文件占用一个单元,用来存放操作文件的必要信息,如读写操作当前位置、文件打开方式、文件操作集等。 进程在打开一个文件时,返回的是文件描述符所在数组的下标,称为文件描述符。 通常,创建子进程时,子进程从父进程继承文件描述符表,前3个描述符0,1,2分别对应标准输入、标准输出、标准错误输出,与进程的控制终端设备对应。通常已经被打开,进行读写操作时无需重新打开。 一个文件可以同时被多个进程打开,它在不同进程中对应的文件描述符以及操作状态也未必相同。
####I/O重定向
程序根据打开文件的描述符对文件进行读写操作,真正完成读写操作的是进程描述符表相应位置中的内容。以输出重定向为例,若将进程描述符表中1号单元的内容替换为打开的文件test,则进程在向标准输出文件输出信息时,原本数据应显示在终端显示器上,但现在这些数据将被输出至文件test。 实现I/O重定向可以通过:
- open close open方法
- 系统函数调用:dupdup2
#####open close open方法
Linux在为进程新打开文件分配描述符时,从下表0开始扫描进程文件描述符表,将打开的文件信息放在找到的第一个空闲单元,并将该下表作为打开文件的描述符。 以标准输入0为例,将标准输入关闭,使得文件描述符表第0号单元成为空闲单元,此时,进程新打开另一个文件,内核将文件描述符表0号单元分配给新打开的文件,并返回描述符0,也就实现了输入重定向。
#####dup和dup2函数
使用dup和dup2函数,只是复制文件描述符,使两个文件描述符指向同一个file结构体,并且file结构体引用计数是2。此时,打开文件的状态保存在同一个file结构体中。而使用open函数两次打开一个文件会存在两份file结构体,分别有各自的状态。
#include <unistd.h>
//从进程文件描述符表中寻找一个可用的最小描述符,返回此描述符
//并复制oldfd对应的File结构指针到新的最小描述符
int dup(int oldfd);
//oldfd需要复制的文件描述符,newfd是复制后oldfd在文件描述符表中新的序号。
//成功返回一个新描述符,否则返回-1
//若newfd已经打开,则先关闭newfd,然后复制oldfd到newfd,使newfd也指向oldfd,此时oldfd和newfd两个描述符共享同一个文件。
int dump2(int oldfd, int newfd);
案例:输出重定向
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
int fileID;
fileID = creat("ls.tst",0640);
if(fileID < 0)
{
fprintf(stderr,"error creating ls.tst\n");
return 1;
}
dup2(fileID,1);
close(fileID);
execl("/bin/ls","ls",NULL);
return 0;
}