Linux系统编程3

dentry和inode

参考:https://bean-li.github.io/vfs-inode-dentry/

image-20231203143825104

innode:

本质是结构体,存储文件的属性信息。如权限、类型、大小、时间、用户、盘块位置等。大多数的inode都存储在磁盘上,少量常用

dentry:

目录项,本质是结构体,存有文件名inode指针。因此一个文件可以有多个dentry项

stat函数

stat函数,用于获取文件属性,即获取的就是inode的内容

1
2
3
#include <sys/types.h>
#include <unistd.h>
int stat(const char *pathname, struct stat *statbuf);

参数

path:文件路径

buf:传出参数,存放文件属性

返回值

成功:0,失败:-1

stat结构体如下

1
2
3
4
5
6
7
8
9
10
11
12
struct stat {
dev_t st_dev; /* 设备号码 */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* 文件模式,文件、目录 */
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
};

判断文件类型,使用对应函数,传入stat结构体中的st_mode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
struct stat sb;
int ret = stat(argy[1], &sb);
if (ret == -1)
{
perror("stat eror");
exit(1);
}

if (S_ISREG(sb.st_mode)) //常规文件
{
printf("It's a regular(n");
} else if (s ISDIR(sb.st mode) //目录
{
printf("It's a dirln");
} else if (S_ISFIFO(sb.st_mode)) //管道
{
printf("It's a pipe n");
}else if (S_ISLNK(sb.st_mode))
{
printf("it's a sym link n"); //链接
}
return 0;
}

link和unlink函数

Linux允许多个目录项共享一个inode,即共享盘块,从而使同一个文件有多个文件名。使用link函数,可以为已经存在的目录创建目录项

1
int link(const char *oldname, const char *newname);

使用unlink可以删除一个文件的目录项

1
int unlink(const char* pathname);

功能详解:unlink从文件系统中中删除一个dentry,若这个dentry是指向这个文件的最后一个链接,并且没有进程处于打开这个文件的状态,则删除这个文件,释放这个文件占用的空间。若这个dentry是指向这个文件的最后一个链接,但是有进程处于打开这个文件的状态,则暂时不删除文件,等到打开这个文件的进程关闭这个文件后,系统才择机删除这个文件。

opendir、readdir函数

opendir打开一个目录文件

1
2
#include <dirent.h>
DIR *opendir(const char*name);

DIR表示目录流类型。

opendir()用来打开参数name 指定的目录, 并返回目录流指针,(相当于文件描述符fd)

对应的关闭时closedir(DIR *name)

readdir函数返回参数dir 目录流的下个目录进入点。

1
struct dirent *readdir(DIR *dirp);
1
2
3
4
5
6
7
8
struct dirent
{
ino_t d_ino; //d_ino 此目录项的inode
ff_t d_off; //d_off 目录文件开头至此目录进入点的位移
signed short int d_reclen; //d_reclen _name 的长度, 不包含NULL 字符
unsigned char d_type; //d_type d_name 所指的文件类型 d_name 文件名
har d_name[256];
};

返回值:成功则返回下个目录项指针。读取到目录文件尾则返回NULL,error不变,读取错误的话返回NULL,并且error被设置

每次读一个项,就会有目录流读取位置就会往后偏移一项,使下次使用readdir可以读下一个目录项指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>

int main(int argc, char *argv[])
{
DIR * dp;
struct dirent *sdp;
dp = opendir(argv[1]);
if(dp==NULL)
{
perror("opendir error");
exit(1);
}

while((sdp = readdir(dp))!=NULL)
{
if(strcmp(sdp->d_name,".")==0||strcmp(sdp->d_name,"..")==0)
continue;
printf("%s\t",sdp->d_name);
}
printf("\n");

closedir(dp);
return 0;
}

rewinddir函数

1
void rewinddir(DIR *dir)

rewinddir()用来设置参数dir 目录流目前的读取位置为原来开头的读取位置.

telldir函数

1
off_t telldir(DIR *dir);

telldir()返回参数dir 目录流目前的读取位置。此返回值代表距离目录文件开头的偏移量,返回值返回下个读取位置。

seekdir函数

1
void seekdir(DIR * dir, off_t offset);

seekdir()用来设置参数dir 目录流目前的读取位置, 在调用readdir()时便从此新位置开始读取. 参数offset 代表距离目录文件开头的偏移量。

总结:实现ls功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <dirent.h>

void Read_file(char *, const int );
void is_file(char *,const int);

void Read_file(char *name, const int depth) //目录
{
DIR *dp;//目录流指针
struct dirent *sdp; //目录项指针
if((dp = opendir(name)) == NULL)
{
perror("opendir error");
exit(1);
}

while((sdp = readdir(dp)) != NULL) //对于目录中的文件,其打开路径都是:目录路径+文件名
{
if(strcmp(sdp->d_name,".")==0||strcmp(sdp->d_name,"..")==0)
continue;
char file_true_path[1000];
char lastChar = name[strlen(name)-1];
if(lastChar=='/')
sprintf(file_true_path,"%s%s",name,sdp->d_name);//拼接
else
sprintf(file_true_path,"%s/%s",name,sdp->d_name);//拼接
is_file(file_true_path,depth);
}
}

void is_file(char *path, const int depth) //depth为文件层次
{
struct stat sb;
if(stat(path, &sb)==-1) //读文件属性,判断是否出错
{
perror("stat error");
exit(1);
}
if(S_ISDIR(sb.st_mode))//目录文件,打印目录
{
for(int i=1;i<=3*depth;i++)
printf(" "); //按深度打印空格
printf("%s\n",path);
Read_file(path,depth+1);
}
else
{
for(int i=1;i<=3*depth;i++)
printf(" "); //按深度打印空格
printf("%s %8ld\n",path, sb.st_size);
}


}

int main(int *argc, char *argv[])
{
if(argc == 1)
is_file(".",1);
is_file(argv[1],1); //目录根路径

return 0;
}

重定向(dup和dup2)

1
2
#include <unistd.h>
int dup(int oldfd); //oldfd是已有的文件描述符

返回值:成功返回新的文件描述符

dup函数为oldfd指向的文件创建一个新的文件描述符副本,它们指向相同的打开文件描述符(参见 open(2)),因此共享文件偏移量和文件状态标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
int fd;
if((fd = open(argv[1],O_RDWR|O_APPEND))==-1)
{
perror("open error");
exit(1);
}

int newfd = dup(fd); //为一个相同文件返回新的文件描述符
printf("newfd = %d\n",newfd);

if (write(newfd, "1234567", 7) == -1)
{
perror("write error");
exit(1);
}
return 0;

}
1
2
#include <unistd.h>
int dup(int oldfd, int newfd); //oldfd是已有的文件描述符,newfd是新的文件描述符

dup2() 系统调用执行与 dup() 类似的任务,但是不使用未使用的最低文件描述符号,而是使用在 newfd 中指定的文件描述符号。如果文件描述符 newfd 之前已经打开,则在被重用之前会被静默关闭。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int main(int argc, char *argv[])
{
int fd;
if((fd = open(argv[1],O_RDWR|O_APPEND))==-1)
{
perror("open error");
exit(1);
}

int newfd = dup2(fd,STDOUT_FILENO); //标准输出的文件描述符所指向得的文件,重定向到fd所指的文件

printf("------------------\n"); //向标准输出写的数据会写入txt文件中

return 0;

}