一、文件存储
一个文件主要由两部分组成,dentry(目录项)和inode
inode本质是结构体,存储文件的属性信息,如:权限、类型、大小、时间、用户、盘块位置…
也叫做文件属性管理结构,大多数的inode都存储在磁盘上。
少量常用、近期使用的inode会被缓存到内存中。
所谓的删除文件,就是删除inode,但是数据其实还是在硬盘上,以后会覆盖掉。
二、文件操作
1. stat函数:获取文件属性,(从inode结构体中获取)
int stat(const char *path, struct stat *buf);
参数:
path: 文件路径
buf:(传出参数) 存放文件属性,inode结构体指针。
返回值:
成功: 0
失败: -1 errno
获取文件大小: buf.st_size
获取文件类型: buf.st_mode
获取文件权限: buf.st_mode
符号穿透:stat会。lstat不会。
#include <stdio. h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread .h>
#include <sys/stat.h>int main(int argc,char *argv[])
{struct stat sbuf;int ret = stat(argv[1], &sbuf);if(ret == -1){perror( "stat error" );exit(1);}printf( "file size: %ld\n" , sbuf.st_size);return 0;
}
查看f.c文件大小执行:
./mystat f.c
2. lstat:查看文件类型,用法同stat
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
#include<sys/stat.h>int main(int argc,char *argv[])
{struct stat sbuf;int ret = lstat(argv[1],&sbuf);if (ret == -1){perror("stat error");exit(1);}if (S_ISREG(sbuf.st_mode)){printf("It's a regular file\n");}else if(S_ISDIR(sbuf.st_mode)){printf("It's a dir\n");}else if (S_ISFIFO(sbuf.st_mode)){printf("It's a pipe\n");}else if(S_ISLNK(sbuf.st_mode)){printf("It's a sym link\n");}return 0;
}
stat会拿到符号链接指向那个文件或目录的属性---符号穿透
查看符号链接文件时不想让其穿透则使用lstat函数
首先对test.c创建一个软连接
ln -s test.c test.s/test.soft
查看改文件类型,执行以下代码:
./mystat test.s/test.soft
out:
It's a sym link
如果使用stat函数,则以上输出为:
It's a dir/It's a regular file
3.link和unlink隐式回收
int link(const char *oldpath, const char *newpath);
link函数,可以为已经存在的文件创建目录项(硬链接)
int unlink(const char *pathname);
- unlink是删除一个文件的目录项dentry,使【硬链接数-1】
- unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放,要等到所有打开文件的进程关闭该文件,系统才会挑时间将该文件释放掉。
编程实现mv命令的改名操作:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>int main(int argc,char *argv[])
{link(argv[1],argv[2]);unlink(argv[1]);return 0;
}
对tets.c改为tets1.c执行以下命令:
./mymv test.c test1.c
三、目录操作
1. getcwd函数
char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作目录
参数:
buf :缓冲区,存储当前的工作目录size :缓冲区大小
返回值:
成功:buf中保存当前进程工作目录位置失败:NULL
2. chdir函数
int chdir(cohst char *path);
功能:修改当前进程(应用程序)的路径
参数:
path:切换的路径返回值:
成功:0失败: -1
示例:
int main (void)
{int ret = -l;char buf[SIZE] ;//1、获取当前进程的工作目录memset(buf, 0, SIZE);if (NULL =getcwd(buf, SIZE)){perror("getcwd error");return 1;}printf( " buf: %s \n", buf);//2.改变当前进程的工作目录ret = chdir( "/home/deng");if (-1 ==ret){perror ("chdir error");return 1;}//3.获取当前进程的工作目录memset(buf,0,SIZE);if (NULL == getcwd(buf,SIZE)){perror ("getcwd error");return 1;}printf("buf: %s\n" , buf);
}
3. opendir函数
DIR *opendir(const char *name) ;
功能:打开一个目录
参数:
name:目录名返回值:
成功:返回指向该目录结构体指针失败:NULL
4. closedir函数
int closedir(DIR *dirp);
功能:关闭目录
参数:
dirp: opendir返回的指针返回值:
成功:0失败: -1
示例:
//目录打开和关闭
int main(void)
{DIR *dir = NULL;//1.打开日录dir = opendir("test");if (NULL == dir){perror ("opendir error");return l;}closedir(dir);return 0;
}
5. readdir函数
struct dirent *readdir(DIR *dirp);
功能:读取目录
参数:
dirp: opendir的返回值返回值:
成功:目录结构体指针失败:NULL
相关结构体说明:
struct dirent{
ino_t d_ino; //此目录进入点的inode
off_t d_off; //目录文件开头至此目录进入点的位移
signed short int d_reclen; // d_name 的长度,不包含NULL字符
unsigned char d_type; // d_type所指的文件类型
char d_name [256]; //文件名};
实现 ls -a 代码如下:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<dirent.h>int main(int argc,char *argv[])
{DIR * dp;dp = opendir(argv[1]);if (dp == NULL){perror("opendir error");exit(1);}struct dirent *sdp;while((sdp = readdir(dp)) != NULL){printf("%s\t",sdp->d_name);}printf("\n"); closedir(dp);return 0;
}
在当前目录下使用命令,执行:./myls ./ 即展示当前目录下的文件及大小
四、递归遍历目录
任务需求:使用opendir closedir readdir stat实现一个递归遍历目录的程序
输入一个指定目录,默认为当前目录。递归列出目录中的文件,同时显示文件大小。
思路分析
递归遍历目录:ls-R.c
1. 判断命令行参数,获取用户要查询的目录名。 int argc, char *argv[1]
argc == 1 --> ./
2. 判断用户指定的是否是目录。 stat S_ISDIR(); --> 封装函数 isFile() { }
3. 读目录: read_dir() {
opendir(dir)
while (readdir()){
普通文件,直接打印
目录:
拼接目录访问绝对路径。sprintf(path, "%s/%s", dir, d_name)
递归调用自己。--> opendir(path) readdir closedir
}
closedir()
}
read_dir() --> isFile() ---> read_dir()
代码示例:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<dirent.h>
#include<stdio.h>
#include<sys/stat.h>#define PATH_LEN 256
//dir=/homeitgan/linux fcn=isfilevoid fetchdir(const char *dir,void (*fcn)(char *))
{char name[PATH_LEN];struct dirent *sdp;DIR *dp;if ((dp = opendir(dir)) == NULL){ //打开目录失败//perror("fetchdir can't open");fprintf(stderr,"fetchdir:can't open %s\n",dir);return;}while ((sdp = readdir(dp)) != NULL){if (strcmp(sdp->d_name,".") == 0 || strcmp(sdp->d_name,"..") == 0){ //防止出现无限递归continue;}if (strlen(dir)+strlen(sdp->d_name)+2>sizeof(name)){fprintf(stderr,"fetchdir:name %s %s too long\n",dir,sdp->d_name);}else{sprintf(name,"%s/%s",dir,sdp->d_name);(*fcn)(name);}}closedir(dp);
}void isfile(char *name)
{struct stat sbuf;if(stat(name,&sbuf) == -1){fprintf(stderr,"isfile:can't access %s\n",name);exit(1);}if ((sbuf.st_mode & S_IFMT) == S_IFDIR){fetchdir(name,isfile);}printf("%8ld %s\n",sbuf.st_size,name);
}int main(int argc,char *argv[])
{if(argc == 1)isfile(".");elsewhile (--argc > 0) //可一次查询多个目录isfile(*++argv);//循环调用该函数处理各个命令行传入的目录return 0;
}
执行 ./ls_R test.c 可查看test.c的文件大小
执行 ./ls_R 可查看当前目录下所有文件的大小
五、文件描述符复制
1. dup和dup2
用来做重定向,本质就是复制文件描述符
重定向:
cat myls.c > out 将myls.c的东西读出再写到out文件中
cat myls.c >> out 将myls.c的东西读出再追加到out文件中
dup()和dup2()用来做重定向,本质就是复制文件描述符,使新的文件描述符也标识旧的文件描述符所标识的文件;
这个过程类似于现实生活中的配钥匙,一把钥匙对应一把锁,然后我们又去配了一把新钥匙,此时两把钥匙都可以打开锁,而dup()和dup2()也一样,原来的文件描述符和新复制出来的文件描述符都指向同一个文件,我们操作这两个文件描述符的任何一个 都能操作它所对应的文件
dup函数
int dup(int oldfd);
功能:
通过oldfd复制出一个新的文件描述符,新的文件描述符是调用进程文件描述符表中最小可用的文件描述符,最终oldfd和新的文件描述符都指向同一个文件。
参数:
oldfd :需要复制的文件描述符oldfd返回值:
成功:新文件描述符失败:-1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>int main(int argc,char *argv[])
{int fd = open("./out",O_RDONLY);//012 ---3int newfd = dup(fd);//4printf("newfd = %d\n",newfd);return 0;
}
make之后执行输出为:newfd = 4
dup2函数:
int dup2(int oldfd,int newfd);
功能:
通过oldfd 复制出一个新的文件描述符newfd,如果成功,newfd和函数返回值是同一个返回值,最终oldfd和新的文件描述符newfd都指向同一个文件。
参数:
oldfd :需要复制的文件描述符
newfd :新的文件描述符,这个描述符可以人为指定一个合法数字(0 - 1023),如果指定的数字已经被占用(和某个文件有关联),此函数会自动关闭close()断开这个数字和某个文件的关联,再来使用这个合法数字。
返回值:
成功:返回newfd失败:返回-1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
#include<fcntl.h>int main(int argc,char *argv[])
{int fd1 = open(argv[1],O_RDWR);//0123---3int fd2 = open(argv[2],O_RDWR);//0123---4int fdret = dup2(fd1,fd2);//返回新文件描述符fd2printf("fdret = %d\n",fdret);//写入fd1指向的文件int ret = write(fd2,"1234567",7);//将屏幕输入重定向给fd1所指向的文件printf("ret = %d\n",ret);dup2(fd1,STDOUT_FILENO);printf("-------------------886\n");return 0;
}
首先新建2个空白文件out和out1
make之后执行以下命令:
./dup2 out out1
首先1234567会写入out文件中,其次再将-------------------886写入out文件
int fdret = dup2(fd1,fd2); 其中fd1 = 3,fd2 = 4
把3拷贝给4,即把4指向3(out文件)
dup2(fd1,STDOUT_FILENO); STDOUT_FILENO = 1
把3拷贝给1,即把1指向3(out文件)
此时指向out的文件描述符有三个
2. fcntl函数
- fcntl用来改变一个【已经打开】的文件的 访问控制属性
- 重点掌握两个参数的使用, F_GETFL,F_SETFL
int (int fd, int cmd, ...)
参数:
fd 文件描述符
cmd 命令,决定了后续参数个数
获取文件状态: F_GETFL
设置文件状态: F_SETFL
返回值:
int flgs = fcntl(fd, F_GETFL);
flgs |= O_NONBLOCK
fcntl(fd, F_SETFL, flgs);
终端文件默认是阻塞读的,这里用fcntl将其更改为非阻塞读:
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h> #define MSG_TRY "try again\n" int main(void)
{ char buf[10]; int flags, n; flags = fcntl(STDIN_FILENO, F_GETFL); //获取stdin属性信息 if(flags == -1){ perror("fcntl error"); exit(1); } flags |= O_NONBLOCK; int ret = fcntl(STDIN_FILENO, F_SETFL, flags); if(ret == -1){ perror("fcntl error"); exit(1); } tryagain: n = read(STDIN_FILENO, buf, 10); if(n < 0){ if(errno != EAGAIN){ perror("read /dev/tty"); exit(1); } sleep(3); write(STDOUT_FILENO, MSG_TRY, strlen(MSG_TRY)); goto tryagain; } write(STDOUT_FILENO, buf, n); return 0;
}
fcntl实现dup描述符:
int fcntl(int fd, int cmd, ....);
cmd: F_DUPFD
参3:
被占用的,返回最小可用的。
未被占用的, 返回=该值的文件描述符。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
#include<pthread.h>int main(int argc, char *argv[])
{int fd1 = open(argv[1],O_RDWR);printf("fd1 = %d\n",fd1);int newfd = fcntl(fd1,F_DUPFD,0);//0被占用,fcntl使用文件描述符表中可用的最小文件描述符返回printf("newfd = %d\n",newfd);int newfd2 = fcntl(fd1,F_DUPFD,7);//7未被占用,返回>=7的文件描述符printf("newfd2 = %d\n",newfd2);int ret = write(newfd2,"YYYYYYYYYYYYY",7);//只能写入7个printf("ret = %d\n",ret);return 0;
}
make之后执行:
./fcntl_dup out
out:
fd1 = 3
newfd = 4
newfd2 = 7
ret = 7