首页 > 系统相关 >操作系统-内存、文件管理

操作系统-内存、文件管理

时间:2024-08-18 10:16:23浏览次数:8  
标签:文件 操作系统 int st 描述符 fd 内存 权限

一、内存管理的层次关系

用户层
STL 自动分配、自动释放 调用C++
C++ new/delete、构造/析构 调用C
C malloc\ calloc\ realloc\ free 调用POSIX\Linux
POSIX sbrk\ brk 调用Kernal
Linux mmap\ munmap 调用Kernal
系统层
Kernal kmalloc\ vmalloc 调用驱动Driver
Driver get_free_page ...

二、进程映像

程序与进程:

  • 程序是存储在磁盘上的可执行文件,当程序被运行时,系统会把程序从磁盘加载到内存中运行,正在运行中的程序称为进程,一个程序可以同时被加载多次,形成多个进程,每个进程相互独立,由操作系统管理

进程映像:进程在内存空间中的分布使用情况就称为进程映像,从低地址到高地址分别是:

  • 代码段 text
    • 存储二进制指令、字面值常量、被const修饰过的原data段的数据
    • 权限r-- 或者 r-x 权限只读
  • 数据段 data
    • 存储初始化过的全局变量和静态变量
  • 静态数据段 BSS
    • 存储未初始化过的全局变量和静态变量
    • 进程一旦加载,此内存段会被操作系统自动清零,默认值是0
    • 如果初始化的值给0,依然还在BSS
  • 堆区 heap
    • 要程序员动态分配、动态释放,从低地址向高地址扩招
    • 使用malloc系列函数进行内存管理
    • malloc系列函数底层调用操作系统的API(brk\ sbrk\ mmap\ munmap
  • 栈区 stack
    • 存储非静态局部变量、块变量,包括函数的参数(除了main函数的参数)、返回值
    • 内存扩展从高地址向低地址扩展
    • 栈区与堆区中间有一段预留空间,一个作用为了预留,第二个是让共享库的内存以及共享内存使用此段内存
  • 命令行参数与环境变量表
    • 里面存储环境变量表以及命令行传给main的参数内容

一、系统调用(系统API)

什么是系统调用

  • 由操作系统向应用程序提供的程序接口信息,本质上就是应用程序与操作系统之间交互的接口。
  • 操作系统的主要功能是为了管理硬件资源和为应用软件的开发人员提供一个良好的环境,使得应用程序具有更好的兼容性,为了达到这个目的,内核提供一套统一的具有一定功能的内核接口函数,称为系统调用\系统函数,以C语言函数的格式提供给用户
  • 操作系统负责把应用程序的请求传给内核,由内核调用具体内核功能完成所需请求,结束后把结果通过操作系统的接口函数的返回值传递给调用者
  • UNIX\Linux大部分系统中的系统功能都是通过系统调用来实现的,相当于调用系统函数,但是它们不是真正意义的函数,当调用它们时,进程立即进入内核态,当调用结束,会重新转入回用户态
time ./<可执行文件名>	#	统计该进程的时间分布
real	0m0.003s	#	总时间
user	0m0.001s	#	用户态执行时间
sys		0m0.000s	#	内核态执行之间
总时间 = 用户态 + 内核态 + 用户态\内核态切换耗时 + 休眠时间

strace ./可执行文件名  #	追踪函数的底层调用过程,以此了解标准库函数中用了哪些系统调用

普通函数与系统函数(系统调用)的区别:

普通函数的调用步骤:
  • 调用者会把要传递的参数压入栈内存
  • 根据函数名也就是该函数的代码段地址,跳转到该函数代码段中执行
  • 从栈内存中弹出传递的参数数据
  • 定义的相关局部变量会入栈到该函数的栈内存进行扩展,并执行相关代码
  • 返回执行结果
  • 销毁该函数的栈内存
  • 返回调用语句处继续执行
系统函数的调用过程:
  • 当执行到系统函数的位置时,会触发软件中断机制
  • 然后进程会转入内核态执行,由内核负责把参数从用户空间拷贝到内核空间
  • 然后内核根据中断编号来执行相应的操作
  • 等执行完毕后,内核再把执行结果从内核空间再拷贝回用户空间中
  • 返回到中断触发位置,转换回用户态继续执行进程

二、一切皆文件

  • 在UNIX\Linux系统中,操作系统把所有服务、设备都抽象成了文件,因为这样可以给各种设备、服务提供同一套统一而简单的操作接口,程序就可以像访问普通文件一样,控制串口、网络、打印机等设备
  • 因此在UNIX\Linux系统中对文件就具有特别重要的意义,一切皆文件,所以大多数情况下,只需要五个基本系统函数操作 open/close/read/write/ioctl,既可以完成对各种设备的输入、输出控制

文件分类:

  • 普通文件 - 包括文本文件、二进制文件、各种压缩包文件等
  • 目录文件 d 类似Windows的文件夹
  • 块设备文件 b 用于保存大块数据的设备,例如磁盘
  • 字符设备文件 c 用于对字符处理的服务、设备,例如 键盘
  • 链接文件 l 类似于Windows的快捷方式
  • 管道文件 p 用于早期进程通信
  • Socket文件\套接字文件 s 用于网络通信

三、文件描述符

什么是文件描述符:
  • 文件描述符是一种非负的整数,用于表示一个打开了的文件
  • 由系统调用(open\creat)返回值,在后续操作文件时可以被内核空间引用去操作对应的文件
  • 它代表了一个内核对象(类似于FILE*),因为内核不能暴露它的内存地址,因此不能返回真正的文件地址给用户
  • 内核中有一张表格,记录了所有被打开的文件对象,文件描述符就是访问这张表格的下标,文件描述符也叫做句柄,是用户操作文件的凭证
  • 内核只要启动,一定会给每个进程打开三个文件描述符,并且是一直默认打开,除非自己手动关闭
//	在<unistd.h> 定义了三个宏

#define STDIN_FILENO    0   //	标准输入 文件指针 stdin 
#define STDOUT_FILENO   1   //	标准输出 文件指针 stdout
#define STDERR_FILENO   2   /* 标准输入 文件指针 stderr

文件描述符与文件指针:
  • 在Linux系统中打开文件后,内存中(内核区间)就会有一个内核对象,也就是记录了该文件相关信息的结构变量,但是内核为了自己的安全不能把它的地址返回给用户,而且内核区间用户是无法直接访问的,就算返回也无权限访问。
  • 而且一个进程可以同时打开多份文件,所以操作系统会在内核区间创造一份索引表,表中的每一项都指向了一个打开的文件内核对象,而用户拿到的文件描述符就是该索引表的下标(主键),因此不同进程之间直接交互文件描述符是没有意义的。
  • C语言标准函数中,使用文件指针代表文件,文件指针指向的区间是进程的用户 区间的一个结构变量(文件结构变量FILE类型),里面记录了该文件的文件描述符还有一些文件缓冲区,因此某种程度上可以理解文件指针就是文件描述符,只是不同的形式,给不同的对象使用而已
int fileno(FILE *stream);
功能:把文件指针转换成文件描述符

FILE *fdopen(int fd, const char *mode);
功能:把文件描述符转换成文件指针

四、文件的创建与打开

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
功能:打开文件
pathname:文件路径
flags:打开文件的方式
返回值:文件描述符

int open(const char *pathname, int flags, mode_t mode);
功能:打开或创建文件
pathname:文件路径
flags:打开文件的方式
mode:创建文件时的权限
返回值:成功返回文件描述符,失败返回负数

注意:open\creat所返回的一定是当前未使用过的最小的文件描述符
注意:一个进程可以同时打开多个文件描述符,最大的数量受limit.h中宏控制数量,在不同的系统标准中不一样,POSIX中不低于16,传统UNIX63个,现代的Linux系统255个

int creat(const char *pathname, mode_t mode);
功能:专门用于创建文件,但是基本不用,因为open可以覆盖它的功能

flags:
	O_RDONLY	只读权限
	O_WRONLY	只写权限
	O_RDWR		读写权限
	O_APPEND	打开文件后位置指针指向末尾
	O_CREAT		文件不存在会创建
	O_EXCL		如果文件存在则创建失败,如果没有该flags,文件存在直接打开
	O_TRUNC		清空内容打开
	O_ASYNC		当文件描述符可读/可写时,会向调用进程发送信号SIGIO

mode: 提供一个三位八进制数表示权限,当flags为O_CREAT时必须提供
	 宏名		权限码
	 S_IRWXU  00700  用户权限码
     S_IRUSR  00400  	读权限
	 S_IWUSR  00200 	写权限
	 S_IXUSR  00100 	执行权限
     S_IRWXG  00070  同组其它用户权限
     S_IRGRP  00040 
     S_IWGRP  00020 
     S_IXGRP  00010 
     S_IRWXO  00007 除了同组的用户外,其它用户的权限
     S_IROTH  00004 
     S_IWOTH  00002 
     S_IXOTH  00001 

#include <unistd.h>
int close(int fd);
功能:关闭文件,成功0 失败-1
问题1:C语言可不可以定义重名函数?
可以,但是需要在不同作用域下才可以重名,同一作用域下不可以
情况1:在不同的源文件中,static声明的函数可以与其他源文件中的普通函数重名
情况2:在函数内定义的函数可以与普通函数重名
问题2: 系统调用为什么可以重名?
因为系统调用本身就不是真正的函数,而是借助了软中断实现内核执行对应的系统操作,而决定执行哪个系统调用操作是由中断编号决定的,而不是名字
问题3:标准库函数中的 r \ r+ \ w \ w+ \ a \ a+ 分别对应系统调用中的flags的哪些标志?
strace ./a.out
r		O_RDONLY
r+		O_RDWR
w		O_WRONLY|O_CREAT|O_TRUNC  0666
w+    	O_RDWR|O_CREAT|O_TRUNC  0666
a		O_WRONLY|O_CREAT|O_APPEND, 0666
a+		O_RDWR|O_CREAT|O_APPEND, 0666

五、文件读写

ssize_t write(int fd, const void *buf, size_t count);
功能:写入文件内容
fd:文件描述符
buf:要写入的数据的内存首地址
count:要写入的字节数
返回值:成功写入的字节数

ssize_t read(int fd, void *buf, size_t count);
功能:从文件中读取数据到内存
fd:文件描述符
buf:存储读取到的数据的内存首地址
count:想要读取的字节数,一般就是buf的大小
返回值:成功读取到的字节数

注意:它们与标准C的 fwrite/fread 很像,但是它们更纯粹直接

练习1:以二进制形式写入10000000个整数到文件中,分别使用标准IO和系统IO来完成,比较它们的速度谁更快,为什么?

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>

void std_io(void)
{
    FILE* fp = fopen("test.txt","w");
    if(NULL == fp)
    {
        perror("fopen");
        return;
    }

    for(int i=0; i<10000000; i++)
    {
        int num = rand();
        fwrite(&num,sizeof(num),1,fp);
    }

    fclose(fp);
}

void sys_io(void)
{
    int fd = open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);
    if(0 > fd)
    {
        perror("open");
        return;
    }

    for(int i=0; i<10000000; i++)
    {
        int num = rand();
        write(fd,&num,sizeof(num));
    }

    close(fd);
}

int main(int argc,const char* argv[])
{
    //std_io();                                                                                  
    sys_io();
}

六、系统IO与标准IO

  • 当系统调用被执行时,需要从用户态转换成内核态,执行完毕后又要转回用户态,如果频繁来回切换会导致性能丢失
  • 在标准IO中,内部维护一个缓冲区(1k,1024字节),要写入的数据先存储到缓冲区中,只有当满足特定条件时才会把缓冲区中数据全部通过系统调用write写入文件,从而降低了系统调用的使用频率,减少了状态的切换,因此标准IO的效率要比直接使用系统IO要快
  • 如果想要提高系统IO的速度,可以尝试自己维护一个缓冲区,先把数据存储到缓冲区,等满后再调用write,这样提高速度
  • 普通情况下建议使用标准IO,因为更快,如果对速度还有更高的要求,可以自己使用系统IO+大缓冲区
void sys_io(void)
{
    int fd = open("test.txt",O_WRONLY|O_CREAT|O_TRUNC,0644);                                                               
    if(0 > fd)
    {
        perror("open");
        return;
    }

    int buf[1024] = {};

    for(int i=0; i<10000000; i++)
    {
        int num = rand();
        buf[i%1024] = num;
        if(0 == (i+1)%1024)
        {
            write(fd,buf,sizeof(buf));
        }
    }

    close(fd);
}

  • UNIX/Linux只有一套读写文件的系统调用,没有像标准C中的文本读写 ,那么可以先把数据转换成字符串(sprintf/sscanf),然后再通过write\read读写,从而实现文本读写的效果
作业1:使用系统IO实现一个带有覆盖检查的cp命令 ./CP a b

七、文件位置指针

  • 与标准IO的文件读写位置指针一样,系统IO时也会有一个表示位置的指针在移动,会随着读写操作的执行向后自动移动
  • 当需要随机位置进行读写操作时,那么需要移动位置指针的位置
off_t lseek(int fd, off_t offset, int whence);
功能:调整文件指针的位置
fd:要调整的文件描述符
offset:
	偏移值
whence:基础位置
	SEEK_SET	文件开头
	SEEK_CUR	当前位置
	SEEK_END	文件末尾
返回值:返回当前位置指针的位置 从文件开头计算该位置的字节数

注意:系统IO中不需要ftell函数的系统调用,因为lseek就能够完成获取位置指针位置的功能
  • 当超过文件末尾的位置再写入数据时,原末尾与最后的数据之间会有一个“数据黑洞”,但是黑洞不占用磁盘大小,但是会计算成文件的大小,不会影响后序的读写

八、文件同步

  • 大多数磁盘I/O都有缓冲区机制,写入文件其实先写入缓冲区,直到缓冲区满才将其排入写队列。降低写操作的次数,提高写操作效率,但是可能会导致磁盘文件与缓冲区数据不同步,可以借助系统调用来强制让磁盘与缓冲区的内容同步:
void sync(void);
功能:将所有被修改过的缓冲区中的数据排入写队列,立即返回,不等待写入磁盘的完成
int fsync(int fd);
功能:只针对文件fd,并且会等待写入完成才返回
int fdatasync(int fd);
功能:只同步文件fd的数据,不同步文件属性,等待写入完成才返回

九、文件描述符的状态标志

#include <unistd.h>
#include <fcntl.h>

int fcntl(int fd, int cmd, ... /* arg */ );
功能:设置或获取文件描述符的状态标志
cmd:
	F_GETFD		获取文件描述符状态标志
	F_SETFD		设置文件描述符状态标志
	F_GETFL		获取文件状态标志
				其中 O_CREAT\O_EXCL\O_TRUNC 获取不了
	F_SETFL		追加文件状态标志
				

十、文件锁

文件锁的意义:
  • 当多个进程同时访问同一个文件时,就有可能造成文件的数据读写混乱,为了解决这一问题可以在读写文件前给文件尝试并加锁
文件锁的限制:
  • 一般情况下,系统提供的文件锁都是劝解锁,内核主要负责文件的加锁和检查是否上锁,而不直接参与锁的控制以及协同操作,这类锁就需要程序员每次用之前都要检查是否被别的进程加锁,再实现并发操作
int fcntl(int fd, int cmd, flock* lock );
功能:对文件的某一部分进行锁操作
struct flock {
 	short l_type;  /* 锁的类型
 				F_RDLCK,读锁
             	F_WRLCK, 写锁
             	F_UNLCK 解锁*/
   short l_whence;  /* 偏移起点
   				SEEK_SET, SEEK_CUR, SEEK_END */
   off_t l_start;   /* 偏移值  锁区起始位置:l_whence+l_start */
   off_t l_len;     /* 锁区长度 0表示锁到文件末尾*/
   pid_t l_pid;     /* 加锁的进程id -1表示让内核自动设置 */
   };
cmd可选:
	F_GETLK		测试lock所表示的锁能否加锁
				如果可以加则讲lock.l_type设置为F_UNLCK
				否则会通过lock.l_type返回当前锁信息
	F_SETLK		设置文件的锁定状态为lock.l_type
				成功返回0,失败返回-1,如果有其他进程持有该锁导致加锁失败,返回EACCES or EAGAIN
	F_SETLKW	设置文件的锁定状态为lock.l_type
				成功返回0,否则一直等待,除非被其它信号打断返回-1
	 

进程A:读锁 进程B:读锁 可共享

进程A:写锁 进程B:读锁 互斥

进程A:读锁 进程B:写锁 互斥

进程A:写锁 进程B:写锁 互斥

十一、复制文件描述符

int dup(int oldfd);
功能:复制文件描述符
oldfd:已经打开的要复制的文件描述符
返回值:返回一个新的文件描述符,是当前可用的文件描述符中最小值,失败-1

int dup2(int oldfd, int newfd);
功能:复制oldfd文件描述符成指定的文件描述符newfd
如果newfd原来已经被占用,则会把它关闭重新复制
返回值:成功0 失败-1
    
注意:复制成功后,相当于两个文件描述符对应同一个打开的文件
复制文件描述符的意义:
  • 复制成功后,相当于两个文件描述符对应同一个打开的文件,可以以此实现很多奇特的操作,例如重定向文件读写、重定向命令 ls >> file
  • 通过把一个已经打开了的文件描述符fd,通过dup2重定向为标准输入0或者标准输出1,此时就会先把0、1文件关闭,然后0\1指向我们刚刚的文件fd,此后,通过输出语句执行时,相当于把本来要输出到屏幕的内容,直接写如到文件fd中,相当于执行write,如果执行输入语句,相当于把本来要从键盘中输入的内容,直接从文件fd中读取,相当于执行了read操作

十二、获取文件属性

int stat(const char *pathname, struct stat *buf);
功能:根据文件路径获取该文件的属性
int fstat(int fd, struct stat *buf);
功能:根据文件描述符获取该文件的属性
int lstat(const char *pathname, struct stat *buf);
功能:根据文件路径获取链接文件的属性

struct stat {
       dev_t  st_dev;         /* 文件的设备ID*/
       ino_t  st_ino;         /* 文件的inode节点号*/
       mode_t st_mode;        /* 文件的类型和权限 */
       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; /* 文件的IO块数量*/
       blkcnt_t  st_blocks;  /* 以512字节为一块,该文件占了几块*/
       struct timespec st_atim;  /* 最后访问时间*/
       struct timespec st_mtim;  /* 最后内容修改时间*/
       struct timespec st_ctim;  /* 最后文件状态属性修改时间*/
       
       		#define st_atime st_atim.tv_sec 时间换算成总秒数     
           #define st_mtime st_mtim.tv_sec
           #define st_ctime st_ctim.tv_sec
       };

st_mode	记录了文件类型和权限
       S_IFMT     0170000   获取文件类型的掩码
       S_IFSOCK   0140000   socket文件	
       S_IFLNK    0120000   软链接文件
       S_IFREG    0100000   普通文件
       S_IFBLK    0060000   块设备文件
       S_IFDIR    0040000   目录
       S_IFCHR    0020000   字符设备文件
       S_IFIFO    0010000   FIFO	管道文件
      /*
	stat(pathname, &sb);
    if ((sb.st_mode & S_IFMT) == S_IFREG) {
               /* Handle regular file */}*/
或者借助提供的宏函数来判断文件类型:
 	S_ISREG(m)  是否是普通文件
	S_ISDIR(m)  目录
	S_ISCHR(m)  字符设备文件
	S_ISBLK(m)  块设备文件
	S_ISFIFO(m) 管道文件
	S_ISLNK(m)  软链接文件
	S_ISSOCK(m) socket文件
	
判断权限:
   S_IRWXU     00700   判断属主的读写执行权限的权限码
   S_IRUSR     00400   owner has read permission
   S_IWUSR     00200   owner has write permission
   S_IXUSR     00100   owner has execute permission

   S_IRWXG     00070   判断属组其他用户的读写执行权限的权限码
   S_IRGRP     00040   group has read permission
   S_IWGRP     00020   group has write permission
   S_IXGRP     00010   group has execute permission

   S_IRWXO     00007   判断其它用户的读写执行权限的权限码
   S_IROTH     00004   others have read permission
   S_IWOTH     00002   others have write permission
   S_IXOTH     00001   others have execute permission


#include <stdio.h>                                                                                                                                                                                      
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <time.h>

//  显示文件类型和权限
const char* mtos(mode_t m)
{
    static char s[11];

    if(S_ISREG(m))
        strcpy(s,"-");
    else if(S_ISDIR(m))
        strcpy(s,"d");
    else if(S_ISCHR(m))
        strcpy(s,"c");
    else if(S_ISBLK(m))
        strcpy(s,"b");
    else if(S_ISFIFO(m))
        strcpy(s,"p");
    else if(S_ISLNK(m))
        strcpy(s,"l");
    else if(S_ISSOCK(m))
        strcpy(s,"s");

    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":"-");

    return s;
}

const char* ttos(time_t t)
{
    static char s[20];
    
    struct tm* time = localtime(&t);
    sprintf(s,"%04d-%02d-%02d %02d:%02d:%02d",
            time->tm_year+1900,
            time->tm_mon+1,
            time->tm_mday,
            time->tm_hour,
            time->tm_min,
            time->tm_sec);
    return s;
}

int main(int argc,const char* argv[])
{
    if(2 > argc)
    {
        printf("User:./a.out <path>\n");
        return 0;
    }

    struct stat st;
    
    if(-1 == stat(argv[1],&st))
    {
        perror("stat");
        return -1;
    }

    printf("设备ID:%llu\n",st.st_dev);
    printf("inode节点号:%lu\n",st.st_ino);
    printf("文件类型:%s\n",mtos(st.st_mode));
    printf("最后访问时间:%s\n",ttos(st.st_atime));
    printf("最后修改时间:%s\n",ttos(st.st_mtime));
    printf("最后状态修改时间:%s\n",ttos(st.st_ctime));
}

十三、文件的权限

测试文件的权限:
int access(const char *pathname, int mode);
功能:测试当前用户对该文件的权限
pathname:要测试的文件路径
mode:
	R_OK	读权限
	W_OK	写权限
	X_OK	执行权限
	F_OK	测试文件是否存在
返回值:存在权限返回0 否则返回-1
修改文件权限:
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
功能:修改文件的权限为mode,mode可以使用提供的宏,或者直接使用一个八进制数表示三组权限
注意:权限都可以修改
文件的权限屏蔽码:
  • 当使用open\creat 创建文件时,无论给什么权限创建都会成功,但是系统中记录有一个权限屏蔽码会对用户创建的文件的权限进行过滤屏蔽,最终创建出来的文件权限要除去文件屏蔽码
//	可以通过命令 umask 查看当前用户的权限屏蔽码
//	可以通过命令 umask 0xxx 修改当前终端这一次的权限屏蔽码为0xxx

mode_t umask(mode_t mask);
功能:给当前进程设置权限屏蔽码
mask:新的屏蔽码
返回值:旧的屏蔽码
注意:只对当前进程有效

注意:屏蔽码只影响open、creat ,对于chmod、fchmod 不受影响

十四、修改文件大小

int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
length:想要修改成的文件字节数
成功返回0,失败返回-1
截短末尾丢弃,加长末尾添0
#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc,const char* argv[])
{
    int fd = open("trunc.txt",O_RDWR|O_CREAT|O_TRUNC,0644);
    if(-1 == fd)
    {
        perror("open");
        return -1;
    }

    //	要映射新文件前,需要让文件大小超过0 才能映射并输入、输出
    if(-1 == ftruncate(fd,100))
    {
        perror("ftruncate");
        close(fd);
        return -1;
    }

    char* str = mmap(NULL,100,PROT_READ|PROT_WRITE,MAP_SHARED|MAP_FILE,fd,0);
    if((void*)-1 == str)
    {
        perror("mmap");
        return 0;
    }
    //  往映射后的虚拟内存写入数据 
    //printf("----%s\n",str);
    sprintf(str,"hello worldxxxxxxxxxxiid\n");
    printf("----%s\n",str);
//  str[0] = 'a';   
    //  取消映射
    munmap(str,100);

    close(fd);
}    
作业:实现一个函数,可以删除文件的[n,m]字节的内容

十五、链接文件

Linux的文件系统会把磁盘分区成主要的两大部分
  • inode信息块
    • 默认128B,里面主要记录文件的权限、大小、所有者、修改时间等基本信息
  • block数据块
    • 默认4Kb,记录了文件名和真正的文件数据内容
  • 每个文件必须拥有一个唯一的inode以及若干个block组成,读取文件需要借助文件所在目录的block中记录的文件inode号,找到该文件的inode,inode中记录了该文件的block位置,从而最终读取文件
什么是软、硬链接文件?
硬链接文件
  • 硬链接文件没有自己inode和block,只是在不同的目录下复制了一份源文件的inode信息,可以通过该inode找到同一份源文件的block
软链接文件
  • 软链接文件会创建自己的新的inode和block,它的inode也是为了找到自己的block,而在它的block中存储的是链接源文件的文件名和inode信息
区别
  • 删除源文件,只是删除源文件的inode块,但是硬链接文件不受影响,而软链接文件就无法访问了
  • 当一个文件的硬链接数删除成0时,文件才被真正的删除
  • 修改硬链接文件内容,源文件也会被修改;而修改软链接的block,不会改变源文件的内容,反而会让软链接无法找到源文件
  • 硬链接不能链接目录,软链接可以
硬链接文件的创建和删除:
int link(const char *oldpath, const char *newpath);
功能:创建硬链接文件
与命令 link \ ln 功能一样

int unlink(const char *pathname);
功能:删除文件的硬链接,文件的硬链接数-1
   
int remove(const char *pathname);
功能:与unlink一致,都可以删除普通文件以及硬链接文件
注意:如果删除的文件正在被打开,则会等待文件关闭后删除
注意:如果删除的是软链接文件,则会只删除软链接文件本身,而不会对源文件有任何影响,而且没有任何一个可以借助软链接来删除链接对象文件的函数
软链接文件的创建与读取:
int symlink(const char *target, const char *linkpath);
功能:创建软链接文件
对应命令:ln -s 

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);
功能:获取到软链接文件自身的block内容,也就是它链接对象的文件名,而不会获取到链接对象的文件内容
如果想要读取链接对象的文件内容,还是通过read\write进行

十六、工作目录

  • 工作目录指的是当前进程所在的目录,它是相对路径的起点,在操作文件时,如果没有提供文件的绝对路径信息,那么会操作工作目录下的文件,一般默认下工作目录就是当前进程的目录
char *getcwd(char *buf, size_t size);
功能:获取当前进程的工作路径,相当于命令 pwd

int chdir(const char *path);
int fchdir(int fd);
功能:修改工作路径,相当于cd

十七、创建、删除、读取目录

int mkdir(const char *pathname, mode_t mode);
功能:创建空白目录
mode:目录的权限,目录必须有执行权限次才能进入

int rmdir(const char *pathname);
功能:删除目录,只能删除空白目录

int chdir(const char *path);
int fchdir(int fd);
功能:修改工作路径,相当于cd

DIR *opendir(const char *name);
DIR *fdopendir(int fd);
功能:打开目录文件
返回值:成功返回目录流指针,失败返回NULL

struct dirent *readdir(DIR *dirp);
功能:从目录流对象中读取一条条目信息
注意:读取完一条条目后,会自动的往后移动,只需要再次调用该函数,即可以读下一条目录
返回值:返回该条目录信息的结构指针或者NULL(读取失败或者读到了目录末尾结束)
结构体 struct dirent里面存储了目录中某个文件的信息
struct dirent {
   ino_t          d_ino;       /* inode节点号*/
   off_t          d_off;       /* 下一条条目的偏移量 注意是磁盘偏移量,而非内存地址*/
   unsigned short d_reclen;    /* 当前条目的长度 */
   unsigned char  d_type;      /* 文件类型 */
   char           d_name[256]; /* 文件名 */
};
d_type的取值:
	DT_BLK  块设备文件
   	DT_CHR  字符设备文件
   	DT_DIR  目录
  	DT_FIFO 管道文件
   	DT_LNK  软链接文件
   	DT_REG  普通文件
   	DT_SOCK socket文件
    DT_UNKNOWN  未知

void rewinddir(DIR *dirp);
功能:复位目录流,设置目录流的位置指针回到开头

long telldir(DIR *dirp);
功能:获取当前目录流的位置

void seekdir(DIR *dirp, long loc);
功能:设置目录流的读取位置,这样可以进行任意条目的获取

int closedir(DIR *dirp);
功能:关闭目录文件
作业2:实现 ls -al 命令的功能
作业3:实现 rm -rf 命令 (建议:先备份虚拟机文件 用户名.vdi)

gcc xxx.c -o RM

RM filename 非空目录也可以删除

标签:文件,操作系统,int,st,描述符,fd,内存,权限
From: https://www.cnblogs.com/sleeeeeping/p/18364643

相关文章

  • 内存表(FDMEMTABLE)
    内存表的优点是快,非常快,号称比BDE的clientdataset快很多。内存表不但快,还可以另存为XML、BIN、CSV等数据。也可直接作为stream传送。所以,追求速度的时候,获得数据后即解除锁定的时候,都可能需要用到内存表。内存表也可用于把来自其他数据库管理系统的数据表存储为FIREDAC的数据表。......
  • Linux 文件系统目录结构介绍和文件管理
    今天给伙伴们分享一下Linux文件系统目录结构介绍和文件管理,希望看了有所收获。我是公众号「想吃西红柿」「云原生运维实战派」作者,对云原生运维感兴趣,也保持时刻学习,后续会分享工作中用到的运维技术,在运维的路上得到支持和共同进步!如果伙伴们看了文档觉得有用,欢迎大家关......
  • 用输入输出流(I/O)流,递归复制和删除多级文件
    一、(I/O)流递归复制一个文件第一种:elseif语句过多,看起来冗余,优点:多级文件一次性复制完整importjava.io.*;//数据源:src/main/java/day15_8_13/haha//目标;src/main/java/LaJipublicclassDiGuiCopy{publicstaticvoidmain(String[]args)throwsException{......
  • 用(I/O)流实现:键盘录入5个学生信息(姓名,语文成绩,数学成绩,英语成绩),按照总分从高到低
    第一步、先创建一个学生类对象,再重写toString方法Student类:publicclassStudent{privateStringname;privatedoublechinese;privatedoublemath;privatedoubleenglish;publicStudent(Stringname,doublechinese,doublemath,doublee......
  • 698java jsp SSM网络办公系统共享文件会议信息工作日程管理(源码+文档+运行视频+讲解视
     项目技术:SSM+Maven+Vue等等组成,B/S模式+Maven管理等等。环境需要1.运行环境:最好是javajdk1.8,我们在这个平台上运行的。其他版本理论上也可以。2.IDE环境:IDEA,Eclipse,Myeclipse都可以。推荐IDEA;3.tomcat环境:Tomcat7.x,8.x,9.x版本均可4.硬件环境:windows7/8/1......
  • 55-Gerber文件的输出
    1.新建Gerber文件输出路径钻孔文件的输出坐标文件输出生成通过浏览可以查看......
  • C#配置文件
    ini文件读取获取执行目录App.config文件读取系统信息ini文件读取ini文件是个啥?.ini文件是InitializationFile的缩写,即初始化文件,是windows的系统配置文件所采用的存储格式,统管windows的各项配置,一般用户就用windows提供的各项图形化管理界面就可实现相......
  • 深入理解JVM运行时数据区(内存布局 )5大部分 | 异常讨论
    前言:    JVM运行时数据区(内存布局)是Java程序执行时用于存储各种数据的内存区域。这些区域在JVM启动时被创建,并在JVM关闭时销毁。它们的布局和管理方式对Java程序的性能和稳定性有着重要影响。  一、由以下5大部分组成1.Heap堆区(线程共享)概念:堆是JVM中最大......
  • 以node / link文件表征的道路网络-----dijkstra算法yyds-----基于南京公路公开数据做
    前文已经基于公开数据,获得了南京的全域高速公路的路网数据,这些以node/link文件表征的道路网络不仅延续了osm地图中所包含的经纬度、名称、容量等信息,还包含了一个重要的道路等级字段“link_type_name”。交通部门一般以高速公路、国省干道、城市道路、乡道农路作为区分......
  • bat 检查某个补丁是否安装成功 ,并将结果输出到日志1.log,支持多个补丁,每次运行log文件
    以下是一个可以检查多个补丁是否安装成功,并将结果输出到 1.log 文件(每次运行重新生成)的BAT脚本示例:bat@echooffrem清空日志文件del1.logrem定义要检查的补丁列表setpatches=KB123456KB789101KB234567rem遍历补丁列表进行检查并输出结果到日志for%%pin......