[学习笔记]达内纪录片day23 文件的映射和元数据

    选择打赏方式

一、内存映射系统调用 mmap

mmap是一种内存映射文件的方法,即将一个文件或者其它对象映射到进程的虚拟地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操作而不必再调用read,write等系统调用函数。

映射的基本单位是页  4k


建立虚拟内存到物理内存或文件的映射

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
参数:
addr:
指定了映射区域的起始地址。进程的虚拟地址 NULL   虚拟地址由内核选择

length:指定了映射区域的长度
prot:
PROT_EXEC  Pages may be executed.
PROT_READ  Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE  Pages may not be accessed.
flags:
MAP_SHARED 共享映射  对映射区内容的更新可以被其他进程(映射同一区域的进程)看到。将内容同步到底层的文件中
MAP_PRIVATE  私有映射  对映射区内容的更新不可以被其他进程(映射同一区域的进程)看到。内容不会同步到底层的文件中。  写时拷贝
MAP_ANONYMOUS  不支持任何文件的映射  fd和offset被忽略 fd=-1,可用于进程通讯
fd:  指定了映射的文件描述符
offset:指定了文件映射的起始位置
返回值:       
成功   返回映射区域的起始地址
失败  MAP_FAILED  错误   errno被设置


解除文件到内存的映射关系
#include <sys/mman.h>
int munmap(void *addr, size_t length);
参数:
addr:  指定了映射区域的起始地址
length:指定了映射区域的长度
返回值:
成功   0
错误   -1   errno被设置



将一个物理地址映射到进程的虚拟地址空间

#include <string.h>
#include <sys/mman.h>
#include <t_stdio.h>
int main(void){
    int prot=PROT_READ|PROT_WRITE;
    int flags=MAP_PRIVATE|MAP_ANONYMOUS;
    //将一块物理地址映射到进程的虚拟地址空间
    void *mp=mmap(NULL,1024,prot,flags,-1,0);
    if(mp==MAP_FAILED)E_MSG("mmap",-1);
    strcpy(mp,"This is a msg!");
    printf("%s\n",(char *)mp);
    munmap(mp,1024);
    return 0;
}

运行结果:

TIM截图20190801220028.png


将文件映射到进程的虚拟地址空间,在内存里对文件的内容进行修改,直接同步到底层的文件中。

#include <t_file.h>
#include <sys/mman.h>
#include <string.h>
int main(int argc,char *argv[]){
    //打开文件 以可读可写的方式
    int fd=open(argv[1],O_RDWR);
    if(fd==-1)E_MSG("open",-1);
    int prot=PROT_READ|PROT_WRITE;
    int flags=MAP_SHARED;
    //建立文件到内存的映射
    void *p=mmap(NULL,6,prot,flags,fd,0);
    if(p==MAP_FAILED)E_MSG("mmap",-1);
    close(fd);//关闭文件描述符,不解除映射
    char *p_ch=(char *)p;
    //strcpy(p_ch,"test msg!");
    *(int *)p=0x30313233;
    //解除文件到内存的映射
    munmap(p,6);
    return 0;
}

匿名内存映射区进程通讯

#include <t_stdio.h>
#include <stdlib.h>
#include <t_file.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc,char *argv[]){
	int len = 4096;
	//创建匿名内存映射区
	void *p=mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
	if(p==MAP_FAILED)E_MSG("mmap",-1);
	//创建子进程
	pid_t pid=fork();
	if(pid==-1)E_MSG("fork",-1);
	if(!pid)printf("来自父进程的消息:%s\n",(char *)p);
	else{
		strcpy((char *)p,"This is a test!");
		wait(NULL);
    	}
    munmap(p,len);
    return 0;
}

运行结果:

TIM截图20190801225624.png

注意:mmap是不能修改文件的长度的,如果文件大小为0b,则无法写入,强写会报总线错误,_lseeki64可以解决此问题


munmap允许对映射区的一部分解映射,但必须按页

mmap/munmap底层不维护任何东西,只是返回一个首地址,所分配内存位于堆中
brk/sbrk底层维护一个指针,记录所分配的内存结尾,所分配内存位于堆中,底层调用mmap/munmap 
malloc底层维护一个线性链表和必要的控制信息,不可越界访问,所分配内存位于堆中,底层调用brk/sbrk
每个进程都有4G的虚拟内存空间,虚拟内存地址只是一个数字,在与实际物理内存建立映射之前是不能访问的
所谓内存分配与释放,其本质就是建立或解除从虚拟内存到物理内存的映射,并在底层维护不同形式的数据结构,以把虚拟内存的占用与空闲情况记录下来

主机字节序(大小端)

不同的CPU有不同的字节序类型,这些字节序是指 整数 在内存中保存的顺序,这个叫做主机序。


最常见的有两种:

1.Little endian:将低序字节存储在起始地址

2.Big endian:将高序字节存储在起始地址


LE little-endian(小端)

最符合人的思维的字节序;

地址低位存储值的低位;

地址高位存储值的高位;

怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说;

低位值小,就应该放在内存地址小的地方,也即内存地址低位;

反之,高位值就应该放在内存地址大的地方,也即内存地址高位;


BE big-endian(大端)

最直观的字节序;

地址低位存储值的高位;

地址高位存储值的低位;

为什么说直观,不要考虑对应关系;

只需要把内存地址从左到右按照由低到高的顺序写出;

把值按照通常的高位到低位的顺序写出;

两者对照,一个字节一个字节的填充进去;


普通人用的桌面电脑,只要是Intel或AMD的x86/x64架构就一定是小端字节序。

很多ARM CPU可以选择数据指令字节序,不过通常也都是运行小端字节序(比如我们的智能手)。

网络设备,像PowerPC核心的一些由器默认运行大端字节序。


例子:在内存中双字 0x01020304(DWORD) 的存储方式 

内存地址 

4000 4001 4002 4003 
LE 04 03 02 01 
BE 01 02 03 04 

例子:如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为

 big-endian  little-endian
0x0000  0x12      0xcd
0x0001  0x23      0xab
0x0002  0xab      0x34
0x0003  0xcd      0x12


大小端测试代码:

#include <stdio.h>
typedef union{
    short s;
    char c;
}u_t;
int main(void){
    u_t u;
    u.s=0x0001;
    (u.c)?printf("small\n"):printf("big\n");
    return 0;
}


二、文件元数据的获取

文件系统中的数据分为两类,分别是数据和元数据。
数据:指的是普通文件中的实际数据;
元数据:指用来描述一个文件的特征的系统数据,诸如访问权限、文件拥有者、以及文件数据块的分布信息等等;

查看文件的元数据信息需要用到一个命令:stat
stat命令的作用为显示文件的状态信息,输出的信息比ls命令输出的信息更加详细。


每个文件都有且只有一个inode。在文件的inode中存放了文件的元数据和文件的数据。每个inode都有自己的一个唯一的编号 inode号
如果两个文件的inode编号一样,这两个文件是硬链接文件

1.链接的概念
简单的理解链接就是快捷方式,在Windows系统中,快捷方式就是指向原文件的一个链接文件,可以让用户从不同的位置来访问原始的文件;原文件一旦被删除或剪切到其他地方后,会导致链接文件失效。但是在Linux系统中这个看似简单的东西和Windows里的可能不大一样。

2.链接的分类

在Linux系统中有软、硬两种链接文件之分。

硬链接(hard link)
我们可以将它理解为一个“指向原始文件inode的指针”,系统不为它分配独立的inode和文件。所以,硬链接文件与原始文件其实是同一个文件,只不过是不同的名字而已。我们每添加一个硬链接,该文件的inode链接数就会增加1;而且只有当该文件的inode连接数为0时,才算彻底将它删除。换言之,由于硬链接实际上是指向原文件的inode的指针,因此即便原始文件被删除,依然可以通过硬链接文件来访问。
1.硬链接,以文件副本的形式存在。但不占用实际空间。
2.不允许给目录创建硬链接
3.硬链接只有在同一个文件系统中才能创建

 
软连接(也称为符号链接[symbolic link])
软链接仅仅包含所链接文件的路径名,因此能链接目录文件,也可以跨越文件系统进行链接。但是,当原始文件被删除后,链接文件也将失效,从这一点上来说与Windows系统中的“快捷方式”具有一样的性质。
1.软链接,以路径的形式存在。类似于Windows操作系统中的快捷方式
2.软链接可以跨文件系统 ,硬链接不可以
3.软链接可以对一个不存在的文件名进行链接
4.软链接可以对目录进行链接


3.ln命令
ln 命令用于创建链接文件,格式为“ln [选项] 目标”,其可用的参数以及作用如下:
-b 删除,覆盖以前建立的链接
-d 允许超级用户制作目录的硬链接
-f 强制执行
-i 交互模式,文件存在则提示用户是否覆盖
-n 把符号链接视为一般目录
-s 软链接(符号链接)
-v 显示详细的处理过程

stat函数族通过stat结构体,向调用者输出文件的元数据

TIM截图20190802205313.png

实际上stat结构体内部对于时间有3个宏定义:

struct timespec st_atim;  /* time of last access */
 struct timespec st_mtim; /*time of last modification*/
 struct timespec st_ctim;/*time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec


struct timespec{
__time_t tv_sec;  秒
__syscall_slong_t tv_nsec; 纳秒

};
__time_t 
__syscall_slong_t   long int 

在结构体里,它们本质上依然是文本替换,在结构体内只是为了让使用更方便,在结构体内外定义并无区别,预处理器并不认识结构体。


stat结构的st_mode成员表示文件的类型和权限,该成员在stat结构中被声明为mode_t类型,其原始类型在32位系统中被定义为unsigned int,即32位无符号整数,但到目前为止,只有其中的低18位有意义用18位二进制数(B17..Bo)表示的文件类型和权限,从高到低可被分为五组
-B17B16B15B14B13B12:文件类型
-B11B10B9:设置用户ID、设置组ID和粘滞
-B8B7B6:拥有者用户的读、写和执行权限
-B5B4B3:拥有者组的读、写和执行权限-B2B1Bo:其它用户的读、写和执行权限


如图所示:

TIM截图20190802214401.png

文件类型:B17..B12
mode_t st_mode;//unsigned int

TIM截图20190802214543.png

设置用户ID、设置组ID和粘滞:B11 B10 B9
mode_t st_mode;//unsigned int

TIM截图20190802214834.png



拥有者用户的读、写和执行权限:B8 B7 B6
mode_t st_mode;//unsigned int


TIM截图20190802215106.png


拥有者组的读、写和执行权限:B5 B4 B3
mode t st_mode;//unsigned int
TIM截图20190802215229.png


其它用户的读、写和执行权限:B2 B1 B0 

mode_t st_mode;//unsigned int

TIM截图20190802215411.png


辅助分析文件类型的实用宏
S_ISREG():是否普通文件
S_ISDIR():是否目录
S_ISSOCK():是否本地套接字
S_ISCHR():是否字符设备
S_ISBLK():是否块设备
S_ISLNK():是否符号链接
S_ISFIFO():是否有名管道

日期和时间转换为字符串
#include <time.h>
char *ctime(const time_t *timep);
参数:
timep:time_t指针
返回值:可读字符串    如:
"Wed Jun 30 21:49:08 1993\n"

uid  转换为用户名
系统的用户信息保存在 /etc/passwd文件中
查看:cat /etc/passwd

冒号分割的7列
1   用户名
2   是否有密码
3   用户的uid
4   用户的组id
5   用户的说明信息
6   用户的家目录
7   执行的shell

获取和uid匹配的一条记录
#include <sys/types.h>
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);
参数:
uid:指定要找的uid
返回值:
成功  返回一个地址
NULL   没有找到
 代表错误产生  errno被设置
 
struct passwd {
               char   *pw_name;        /* username */
               char   *pw_passwd;    /* user password */
               uid_t   pw_uid;          /* user ID */
               gid_t   pw_gid;        /* group ID */
               char   *pw_gecos;      /* user information */
               char   *pw_dir;        /* home directory */
               char   *pw_shell;      /* shell program */
           };

gid  转换为组名
用户组的信息存储到/etc/group文件中
tarena:x:1000:
root:x:0:
adm:x:4:syslog,tarena

冒号分割的四列
1  用户组的名字
2  密码
3  用户组的id
4  用户组的用户成员


根据gid找到用户组的信息
#include <sys/types.h>
#include <grp.h>
struct group *getgrgid(gid_t gid);
参数:
gid:指定具体的gid,找到跟这个gid匹配的记录
返回值:
成功  返回一个地址,指向strutc group结构体的指针
NULL 代表没有找到匹配的
错误   errno被设置

struct group {
               char   *gr_name;        /* group name */
               char   *gr_passwd;     /* group password */
               gid_t   gr_gid;           /* group ID */
               char  **gr_mem;      /* NULL-terminated array of pointers to names of group members */
           };


演示源码:

#include <t_file.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
int main(int argc,char *argv[]){
    struct stat buf;
    //获取文件的元数据
    int s=stat(argv[1],&buf);
    if(s==-1)E_MSG("stat",-1);
    //将文件的信息已经存储到buf的空间里了
    printf("inode:%lu\n",buf.st_ino);
    printf("hard link:%u\n",buf.st_nlink);
    printf("inode:%ld\n",buf.st_size);
    printf("modify time:%ld\n",buf.st_mtim.tv_sec);
    printf("modify time:%ld\n",buf.st_mtime);
    printf("modify time:%s",ctime(&buf.st_mtime));
    printf("uid=%u\n",buf.st_uid);
    struct passwd *pw=getpwuid(buf.st_uid);
    printf("username:%s\n",pw->pw_name);
    printf("gid=%u\n",buf.st_gid);
    printf("group name:%s\n",(getgrgid(buf.st_gid))->gr_name);

    //处理文件的类型和文件的权限
    printf("mode:%o\n",buf.st_mode);
#if 0
    switch(buf.st_mode&S_IFMT){
        case S_IFSOCK:
            printf("s");
            break;
        case S_IFREG:
            printf("-");
            break;
        case S_IFDIR:
            printf("d");
            break;
        default:
            break;
    }
#endif
    if(S_ISREG(buf.st_mode))printf("-");
    if(S_ISDIR(buf.st_mode))printf("d");

    (buf.st_mode&S_IRUSR)?printf("r"):printf("-");
    (buf.st_mode&S_IWUSR)?printf("w"):printf("-");
    (buf.st_mode&S_IXUSR)?printf("x"):printf("-");
    printf("\n");
    return 0;
}


代码实现ls-l命令功能:



THE END!


版权声明:若无特殊注明,本文皆为《 8964CN 》原创,转载请保留文章出处。
本文链接:[学习笔记]达内纪录片day23 文件的映射和元数据 http://www.8964cn.net/?post=85
正文到此结束

热门推荐

发表吐槽

你肿么看?

你还可以输入 250 / 250 个字

嘻嘻 大笑 可怜 吃惊 害羞 调皮 鄙视 示爱 大哭 开心 偷笑 嘘 奸笑 委屈 抱抱 愤怒 思考 日了狗 胜利 不高兴 阴险 乖 酷 滑稽

评论信息框

吃奶的力气提交吐槽中...


既然没有吐槽,那就赶紧抢沙发吧!