文件的元数据
#include
int stat(char const* path,struct stat* buf);
int fstat(int fd,struct stat* buf);
int lstat(char const* path, struct stat* buf);
功能:从 i 节点中提取文件的元数据,即文件的属性信息
参数: path 文件路径
buf 文件元数据结构,存在这里,被称为输出型参数
fd 文件描述符
返回值:成功返回0,失败返回-1
struct stat s;
stat(".....',&s); //保存元数据
lstat()函数与另外两个函数的区别在于它不跟踪符号链接(软连接)
例:
abc.txt ------> xyz.txt abc.txt文件是xyz.txt文件的符号链接
stat("abc.txt",...) //得到xyz.txt文件的元数据
lstat("abc.txt"); //得到abc.txt文件的元数据
xyz有自己的i节点和数据块
而abc也有自己的 i 节点和数据块, 它的数据块,记录着xyz的完整路径
stat通过abc找到xyz文件的元数据
而lstat则不去追踪,只获得abc的元数据
stat函数通过stat结构体,向调用者输出文件的元数据
struct stat{
dev_t st_dev; //设备ID
ino_t st_ino; //i节点号
mode_t st_mode; //文件的类型和权限 重点 -普通d目录 l符号链接 b块设备 c字符设备 s套接字 p管道 权限:rwx
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;//I/O块字节数
blkcnt_t st_blocks;//存储块数
time_t st_atime; //最后访问时间
time_t st_mtime; //最后修改时间
time_t st_ctime; //最后状态改变时间
}
文件元数据结构
1.stat结构的st_mode成员表示文件的类型和权限,该成员在stat结构中被声明为mode_t类型,其原始类型在32位系统中被定义为unsigned int,即32位无符号整数,但到目前为止,只有其中的低16位有意义
2.用16位二进制数(B15...BO)表示的文件类型和权限,从高到低可被分为五组B15 - B12∶文件类型
B11- B9 ︰设置用户ID,设置组ID,粘滞
B8 - B6∶拥有者用户的读、写和执行权限
B5 -B3∶拥有者组的读、写和执行权限
B2 -BO:其它用户的读、写和执行权限
文件类型:B15-B12
设置用户ID、设置组ID和粘滞:B11 -B9
1.系统中的每个进程其实都有两个用户ID,一个叫实际用户ID,一个叫有效用户ID
2.进程的实际用户ID继承自其父进程的实际用户ID。当一个用户通过合法的用户名和口令登录系统以后,系统就会为他启动一个Shell进程,Shell进程的实际用户ID就是该登录用户的用户ID。该用户在Shell下启动的任何进程都是Shell进程的子进程,自然也就继承了Shell进程的实际用户ID
3.一个进程的用户身份决定了它可以访问哪些资源,比如读、写或者执行某个文件。但真正被用于权限验证的并不是进程的实际用户ID,而是其有效用户ID。一般情况下,进程的有效用户ID就取自其实际用户ID,可以认为二者是等价的
4.但是,如果用于启动该进程的可执行文件带有设置用户ID位,即B11位为1,那么该进程的有效用户ID就不再取自其实际用户ID,而是取自可执行文件的拥有者用户ID
5.系统管理员常用这种方法提升普通用户的权限,让他们有能力去完成一些本来只有root用户才能完成的任务。例如,他可以为某个拥有者用户为root的可执行文件添加设置用户ID位,这样一来无论运行这个可执行文件的实际用户是谁,启动起来的进程的有效用户ID都是root,凡是root用户可以访问的资源,该进程都可以访问。当然,具体访问哪些资源,以何种方式访问,还要由这个可执行文件的代码来决定。作为一个安全的操作系统,不可能允许一个低权限用户在高权限状态下为所欲为。如通过passwd命令修改口令
6.带有设置用户ID位的不可执行文件︰没有意义。
7.带有设置用户ID位的目录文件︰没有意义。
8.与设置用户ID位的情况类似,如果一个可执行文件带有设置组ID位,即B10位为1,那么运行该可执行文件所得到的进程,它的有效组ID同样不取自其实际组ID,而是取自可执行文件的拥有者组ID
9.带有设置组ID位的不可执行文件∶某些系统上用这种无意义的组合表示强制锁。
10.带有设置组ID位的目录文件︰在该目录下创建文件或子目录,其拥有者组取自该目录的拥有者组,而非创建者用户所隶属的组。
11.带有粘滞位(B9)的可执行文件,在其首次运行并结束后,其代码区被连续地保存在磁盘交换区中,而一般磁盘文件的数据块是离散存放的。因此,下次运行该程序可以获得较快的载入速度
12.带有粘滞位(B9)的不可执行文件:没有任何意义
13.带有粘滞位(B9)的目录︰除root以外的任何用户在该目录下,都只能删除或者更名那些属于自己的文件或子目录,而对于其它用户的文件或子目录,既不能删除也不能改名,如/tmp目录
粘滞位是老技术,无所谓,拓个眼界就行
以下为总结:
a.out --->元数据 ----->st_mode ---->第12位为 1 --->拥有设置用户ID位
a.out执行起来 --->形成进程,对于进程来讲,有两个ID:
实际用户ID: 取决于当前计算机的登陆身份
有效用户ID: 一般情况下等效于实际用户ID
有效用户ID 决定了进程对系统资源的访问能力
如果是root用户创建的文件,我们也是可以使用的,比如各种各样的命令就是root创建的(mv cp 等),我们也可以使用
如果一个文件拥有设置用户ID位,那么该文件执行起来之后所形成的进程,他的有效用户ID,不再取自于登陆身份,而是取决于形成该进程的可执行程序的创建者身份
所以对于有效用户ID ,一般情况下取自于登陆用户身份
不一般时如a.out有设置用户ID位时,则取决于 a.out的用户身份(谁创建的)
如果想改密码,就得暂时修改权限,我们到user下bin查看一下password
看这里,rws,是s而不是x,说明这个程序可以设置用户ID位
则当运行时,有效运行ID则变为root
-------------------------------------------------------------------
比如有个目录
组身份是张三,目录拥有设置组id位,那么其下所有创建的文件,其组身份都是张三
拥有者用户的读、写和执行权限:B8 - B6
拥有者组的读、写和执行权限:B5 - B3
其他用户的读、写和执行权限:B2 - B0
辅助分析文件类型的实用宏
S_ISREG() :是否普通文件 -
S_ISDIR() :是否目录 d
S_ISSOCK() :是否本地套接字 s
S_ISCHR() :是否字符设备 c
S_ISBLK() :是否块设备 b
S_ISLNK() :是否符号链接 l
S_ISFIFO() :是否有名管道 p
当我们获得st_mode的数值时,丢尽辅助宏,可以判断是否为以上功能
S 和 s都是表示文件拥有设置用户ID的能力,S是文件枚举没有执行权限,s是文件有执行权限
通过 man 3 localtime查看 localtime()函数,其参数是time_t,进行时间转换
time_t t;
struct tm* local = localtime(&t)
然后利用sprintf(); // 把一个字符串输出到指定地区进行存储
static char time[20]
sprintf(time,"%04d-%02d-%02d %02d:%02d:%02d",local->tm_year-1900,local->tm_mon +1,local->mday,local->tm_hour,local->tm_min,local->sec); //占4位 2位 2位,少补0
注意代码:非常重要
//文件的元数据
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<time.h>
//转换类型和权限
char* mtos(mode_t m){
// m --> -rw-rw-r--
static char s[11];
//我们可以利用辅助宏进行判断文件类型和信息
if(S_ISDIR(m)){
strcpy(s,"d");
}
else
if(S_ISSOCK(m)){
strcpy(s,"s");
}
else
if(S_ISCHR(m)){
strcpy(s,"c");
}
else
if(S_ISBLK(m)){
strcpy(s,"b");
}
else
if(S_ISLNK(m)){
strcpy(s,"l");
}
else
if(S_ISFIFO(m)){
strcpy(s,"p");
}
else{
strcpy(s,"-");
}
//我们将st_mode的值与宏做按位与,结构为真则有此权限,没有则无此权限
strcat(s,m & S_IRUSR ? "r" : "-");
strcat(s,m & S_IWUSR ? "w" : "-");
strcat(s,m & S_IXUSR ? "x" : "-");
strcat(s,m & S_IRGRP ? "r" : "-");
strcat(s,m & S_IWGRP ? "w" : "-");
strcat(s,m & S_IXGRP ? "x" : "-");
strcat(s,m & S_IROTH ? "r" : "-");
strcat(s,m & S_IWOTH ? "w" : "-");
strcat(s,m & S_IXOTH ? "x" : "-");
if(m & S_ISUID){
s[3] = (s[3] == 'x') ? 's' : 'S';
}
if(m & S_ISGID){
s[6] = (s[6] == 'x') ? 's' : 'S';
}
if(m & S_ISVTX){
s[9] = (s[9] == 'x') ? 't' : 'T';
}
return s;
}
//时间转换
//通过man 3 localtime查看函数localtime()函数,参数是time_t类型,可以进行转换
char* ttos(time_t t){
static char time[20];
struct tm* local = localtime(&t);
sprintf(time,"%04d-%02d-%02d %02d:%02d:%02d",local->tm_year + 1900,local->tm_mon + 1,
local->tm_mday,local->tm_hour,local->tm_min,local->tm_sec);
return time;
}
int main(int argc,char* argv[]){
// ./a.out hello.txt
if(argc < 2){
fprintf(stderr,"用法:./a.out <文件名>\n");
return -1;
}
struct stat buf;//输出型参数,用来存储所获取的元数据
if(stat(argv[1],&buf) == -1){
perror("stat");
return -1;
}
//针对stat结构体中的文件类型和权限
//获取st_mode的无符号整型值是没有意义的,我们需要的是它的类型和权限表示出来
//还有time_t 时间 也是一个整数,同样没有意义,要转换成年月日时分秒
printf(" 设备ID:%lu\n",buf.st_dev);
printf(" i节点号:%ld\n",buf.st_ino);
printf(" 类型和权限:%s\n", mtos(buf.st_mode));
printf(" 硬链接数:%lu\n",buf.st_nlink);
printf(" 用户ID:%u\n",buf.st_uid);
printf(" 组ID:%u\n",buf.st_gid);
printf(" 特殊设备ID:%lu\n",buf.st_rdev);
printf(" 总子节数:%ld\n",buf.st_size);
printf(" I/O块字节数:%ld\n",buf.st_blksize);
printf(" 存储快数:%ld\n",buf.st_blocks);
printf(" 最后访问时间:%s\n",ttos(buf.st_atime));
printf(" 最后修改时间:%s\n",ttos(buf.st_mtime));
printf(" 最后改变时间:%s\n",ttos(buf.st_ctime));
return 0;
}
内存映射文件
跟之前物理内存和虚拟地址映射类似,但是这里是往磁盘文件上映射,虚拟文件和磁盘文件进行映射,即往磁盘文件里写数据
与write相比,就是快,内部不需要很多复制形式的过程
还可以解决进程与进程之间通信的问题
因为两个虚拟地址映射到同一文件,就进行一个通信了,不会像物理内存那样会映射到不同的位置
#include
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);
功能:建立虚拟内存到物理内存或磁盘文件得映射
参数:
1.start:映射区虚拟内存的起始地址,NULL系统自动选定后返回。
2.length :映射区字节数,自动按页圆整。
3.prot:映射区操作权限,可取以下值:
PROT_READ -映射区可读
PROT_WRITE-映射区可写
PROT_EXEC-映射区可 执行
PROT_NONE-映射区不可访问
4.flags:映射标志,可取以下值︰
MAP_ANONYMOUS-匿名映射,将虚拟内存映射到物理内存而非文件,忽略fd和offset参数
MAP_PRIVATE–对映射区的写操作只反映到缓冲区中并不会真正写入文件,然后再同步到磁盘当中,即写的时候,别的进程是看不到的
MAP_SHARED -对映射区的写操作直接反映到文件中
上边两个二选一
MAP_DENYWRITE-拒绝其它对文件的写操作
MAP_FIXED-若在start上无法创建映射,则失败(无此标志系统会自动调整)
5.fd :文件描述符
6.offset :文件偏移量,自动按页(4K)对齐,只能是4096整数倍
返回值:成功返回映射区虚拟内存的起始地址,失败返回MAO_FAILED(-1)
#include
int munmap(void* start,size_t length);
功能:解除虚拟内存到物理内存或磁盘文件的映射
参数:
start:映射区虚拟内存的起始地址
length:映射区字节数,自动按页取整
返回值:成功返回0,失败返回-1
可以解除部分映射,但必须按页取整
//内存映射文件
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/mman.h>
int main(void){
//打开文件
int fd = open("./shared.txt",O_RDWR | O_CREAT | O_TRUNC,0664);
if(fd == -1){
perror("open");
return -1;
}
//修改文件大小
if(ftruncate(fd,4096) == -1){
perror("ftruncate");
return -1;
}
//完成虚拟内存和磁盘文件的映射
char* start = mmap(NULL,4096,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(start == MAP_FAILED){
perror("mmap");
return -1;
}
//操作文件
memcpy(start,"0123456789",10); //内存拷贝,第一个参数是地址,把第二个参数拷贝到第一个参数,第三个参数是长度
//等价于write 操作文件这里,如果想写入文件,可以用strcpy,sprintf等很多方法
//内存映射在拷贝时,不是write和read这种专门操作文件的函数,在运行时,如果本身文件的内存大小不足,则不足以进行拷贝
printf("%s\n",start); // read
//解除映射
if(munmap(start,4096) == -1){
perror("munmap");
return -1;
}
//关闭文件
close(fd);
return 0;
如果不提前给文件足够大小时(ftruncate()),就会出现段错误
如果再写,则其映射虚拟地址的指针是需要进行更改的,否则还是从头开始写