9.文件的元数据,内存映射文件

文件的元数据

#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()),就会出现段错误

如果再写,则其映射虚拟地址的指针是需要进行更改的,否则还是从头开始写 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值