#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#include #include #include #include //转换类型和权限 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:%lun",buf.st_dev); printf(" i节点号:%ldn",buf.st_ino); printf(" 类型和权限:%sn", mtos(buf.st_mode)); printf(" 硬链接数:%lun",buf.st_nlink); printf(" 用户ID:%un",buf.st_uid); printf(" 组ID:%un",buf.st_gid); printf(" 特殊设备ID:%lun",buf.st_rdev); printf(" 总子节数:%ldn",buf.st_size); printf(" I/O块字节数:%ldn",buf.st_blksize); printf(" 存储快数:%ldn",buf.st_blocks); printf(" 最后访问时间:%sn",ttos(buf.st_atime)); printf(" 最后修改时间:%sn",ttos(buf.st_mtime)); printf(" 最后改变时间:%sn",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#include #include #include #include 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("%sn",start); // read //解除映射 if(munmap(start,4096) == -1){ perror("munmap"); return -1; } //关闭文件 close(fd); return 0;
如果不提前给文件足够大小时(ftruncate()),就会出现段错误
如果再写,则其映射虚拟地址的指针是需要进行更改的,否则还是从头开始写



