Linux操作系统 内存管理、用户操作和文件操作
内存管理、用户操作和文件操作
预备知识:
1、Linux系统的内存分为物理内存和虚拟内存。物理内存是指安装在计算机当中的主存储器;虚拟内存是一段虚拟的逻辑上连续的储存空间,虚拟内存是由多个内存碎片组成,只有正在使用的虚拟内存被存放在内存上,对于暂时不使用的虚拟内存空间其实是储存在外存中。虚拟内存空间地址和实际的物理内存空间地址存在某种逻辑上的关系,如果虚拟内存空间地址的内容将被使用,通过逻辑关系可以计算出此部分内容对应的实际物理内存空间,然后将内容加载到内存中。虚拟内存在一定程度上独立于物理内存。
2、计算机的物理内存空间有限(虚拟内存只要运行部分占用物理内存空间)而且不同PC的物理内存是不一样(通过虚拟内存空间,进程中代码的访存操作的地址全部是这个内存空间的地址),所以进程使用的是虚拟内存空间。
3、内存的最小粒度是页,进程虚拟地址空间和内存的映射也是以页为单位。页(面)的大小称为页面的大小,大小应该为2的幂。页号P=A/L;A表示逻辑地址空间中的地址(虚拟内存空间地址),L表示页面大小。
4、物理块:将内存的物理地址空间划分为若干块,称为物理块,物理块与页(面)一一对应。
5、页表储存在物理内存中,由操作系统维护。CPU中有一个页表寄存器,里面存放着当前进程页表的起始地址和页表长度,页表起始地址和页表长度的信息在进程不被CPU执行的时候,存放在其PCB内。每个进程都有页表,每个进程都有一个虚拟的内存空间,虚拟内存空间按页为基本单位,划分为页面长度个基本单位。真实的物理内存空间按框为基本单位(页和框的大小都是一样的,如Windows系统都是4KB),页表中的每一项都是一个页和一个框的对应。操作系统访问进程地址空间由虚拟地址空间到物理地址空间的实际过程是:一、通过虚拟地址:A和页面大小:L ,计算CPU访问的页表号:(A/L) 和页表号:(A/L) ;二、操作系统访问页表寄存器,获取起始地址和页表长度,对比页表长度和A/L,确定是否在页表方位内,然后起始地址+页表号和页表项长度相乘=准确的页表项地址,从而获取该页表项中的页对应的框。CPU拿到框的起始地址之后,再加上页内偏移地址,访问到最终的目标地址。CPU访问了两次物理内存,第一次、框的起始地址,第二次、最终物理地址。快速缓冲区寄存器TLB,存放了近期访问过的页表项,CPU发起一次访问时,先到TLB中查询是否存在对应的页表项,如果有就直接返回了。整个过程只需要访问一次内存。
内存管理:
1、虚拟内存空间分配情况
2、虚拟内存空间分配情况
1、程序代码区,存放程序的代码
2、全局变量区(清零区),存放全局变量和静态变量。程序载入时,系统会自动将这部分的存储数据清零。
3、堆区,存放用户申请得到的内存空间,进程运行过程向高地址增长。操作系统有一个记录空闲内存地址的链表,<code>系统受到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给用户。节点内存空间可能大于用户需求的内存空间,导致出现内存碎片,系统自动将内存碎片重新放回空闲链表中。
4、栈区,存放程序运行时的函数调用栈和函数的参数、局部变量等信息,进程运行过程向低地址增长。
5、内核空间,存放内核的代码和数据,所有进程共享。
注:
堆上的数据空间是用户申请的,只要用户不释放空间,就一直可以访问到,如果忘记释放会造成内存泄露。
栈上的空间是系统自动分配自动回收,栈上的数据的生存周期只是在函数的运行过程中,运行后就释放掉,不可以再访问;
3、内存操作相关函数
1、malloc、在堆中,分配用户申请内存空间,基本单位是字节,内存空间大小是size字节,return内存空间的首地址
2、calloc、在堆中,分配用户申请内存空间,基本单位是字节,内存空间大小是n*size字节,return内存空间的首地址
3、free、释放堆中malloc和calloc申请的内存空间
4、mmap、建立内存映射、将文件映射到内存,通过内存操作完成对文件的操作。
mmap函数把一个文件或一个POSIX共享内存区对象映射到调用进程的地址空间,
5、munmap、取消由mmap映射的内存区域
6、getpagesize、获取内存页面大小,单位为字节
7、bcopy/mencpy、内存复制、将指定大小的内存原地址的资源复制到目标地址
8、menmove、内存复制
9、memcopy、内存复制并查找字符(int c)、返回出现目标地址中出现编码为c的字符所在地址的下一个地址,如不存在,返回NULL。
10、menchr、在指定内存区域中查找字符
11、bcmp/memcmp、内存比较,按字典顺序逐个字节进行比较
12、bzero、将指定内存区域用0填充
13、memset、将指定内存区域用指定字节(int c )进行填充。
用户操作:
预备知识:
1、用户管理可以保证系统文件的安全性又要减少系统的开销(系统的存储空间等系统开销),Linux系统的文件权限分成三个等级:文件拥有者,文件拥有者所在的用户组,系统所有其他用户。
2、针对于同一文件,同一等级的用户具有相同的权限。
3、用户组、Linux系统中的用户组(group)就是具有相同特性的用户(user)集。
用户操作相关的数据结构:
1、相关数据结构:
struct passwd
{
char * pw_name; /* Username, POSIX.1 */
char * pw_passwd; /* Password */
uid_t pw_uid; /* User ID, POSIX.1 */
gid_t pw_gid; /* Group ID, POSIX.1 */
char * pw_gecos; /* Real Name or Comment field */ 用户的全名
char * pw_dir; /* Home directory, POSIX.1 */ 用户主目录
char * pw_shell; /* Shell Program, POSIX.1 */ 用户使用的Shell程序,规定操作界面
}
struct group
{
char * gr_name; /* 用户组名 */
char * gr_passwd; /* 用户组密码 */
gid_t gr_gid; /* 用户组ID */
char ** gr_mem; /*一个字符串数组,用户组的成员列表 */
}
注:
一、 /etc/passwd文件中每行定义一个用户账号,有多少行就表示多少个账号,在一行中可以清晰的看出,各内容之间又通过”:”号划分了7个字段,这7个字段分别定义了账号的不同属性。如:yinminbo : x : 1000 : 1000 : yinminbo : /home/yinminbo : /bin/bash;root : x : 0 : 0 : root : /root : /bin/bash;geoclue : x : 991 : 987 : User for geoclue : /var/lib/geoclue : /sbin/nologin
1、字段1:帐号名,这是用户登陆时使用的账户名称,在系统中是唯一的,不能重名
2、字段2:密码占位符x;早期的unix系统中,该字段是存放账户和密码的,由于安全原因,后来把这个密码字段内容移到/etc/shadow中了。这里可以看到一个字母x,表示该用户的密码是/etc/shadow文件中保护的。
3、字段3:UID;范围是0-65535
4、字段4:GID;范围是0-65535;当添加用户时,默认情况下会同时建立一个与用户同名且UID和GID相同的组。
5、字段5:用户说明;这个字段是对这个账户的说明
6、字段6:宿主目录;用户登陆后首先进入的目录,一般与"/home/用户名"这样的目录
7、字段7:登录Shell 当前用户登陆后所使用的shell,在centos/rhel系统中,默认的shell是bash;如果不希望用户登陆系统,可以通过usermod或者手动修改passwd设置,将该字段设置为/sbin/nologin 即可。大多数内置系统账户都是/sbin/nologin,这表示禁止登陆系统。这是出于安全考虑的。
二、UID:0:超级用户(root)账号UID;UID:1~499:程序用户账号UID;UID 500~65535:普通账户UID
三、 /etc/group文件内容包含4个字段,每个字段的含义如前面的数据结构中的group的定义。示例:yinminbo : x : 1000 : yinminbo ;root : x : 0 : ;geoclue : x : 987 :
用户操作相关的函数:
1、getuid、获取当前进程的用户ID
2、geteuid、获取当前进程的有效用户ID,用户ID和有效用户ID在通常情况下,两者是一致的。用户ID是一种固有属性,有效用户ID是一种动态属性。
3、getpwuid、根据用户ID获取系统中某用户的详细信息。return:记录用户ID的详细信息的passwd结构体指针,失败返回NULL。
4、getpwnam、根据用户名获取系统中某用户的详细信息,return:记录用户ID的详细信息的passwd结构体指针,失败返回NULL。
5、getpwent、依次获得系统某用户的详细信息,每一次调用都返回一个。应该有指针!用户信息读取指针,每读取依次,用户信息读取指针加1。
6、setpwent、复位用户信息读取指针。
7、setuid、设置当前进程的用户ID。只有当前有效用户ID是0(root),该函数才有效。
8、seteuid、设置当前进程的有效用户ID。
用户组操作相关的函数:
1、getgid、获取当前进程的用户组ID
2、getegid、获取当前进程的有效用户组ID,用户组ID和有效用户组ID在通常情况下,两者是一致的。用户ID是一种固有属性,有效用户ID是一种动态属性。
3、getgrgid、根据用户组ID获取系统中某用户组的详细信息。return:记录用户组ID的详细信息的group结构体指针,失败返回NULL。
4、getgrnam、根据用户组名获取系统中某用户组的详细信息,return:记录用户组ID的详细信息的group结构体指针,失败返回NULL。
5、getgrent、依次获得系统某用户组的详细信息,每一次调用都返回一个。应该有指针!用户组信息读取指针,每读取依次,用户组信息读取指针加1。
6、setgrent、复位用户组信息读取指针。
7、setgid、设置当前进程的用户组ID。只有当前有效用户组ID是0(root),该函数才有效。
8、setegid、设置当前进程的有效用户组ID。
文件操作:
预备知识:
1、文件:计算机当中信息的集合,文件是数据的载体。文件可以是文本文档、图片和程序等等。操作系统的文件系统用于实现用户对文件的维护和管理,文件系统是操作系统提供给用户操作的界面。
2、“一切皆为文件”的抽象给系统的使用和编程提供统一的界面和接口。
3、/(根目录下的各个文件夹的作用):
注:
1、/usr不是user的缩写,usr是Unix Software Resource的缩写, 也就是Unix操作系统软件资源所放置的目录。
2、lib一般放常用系统库文件,也就是后缀.lib的文件,include一般包含头文件目录;bin表示binary目录,一般都是dll,exe等。
3、proc包含系统状态信息
4、bin包含Linux系统常用的Linux命令,这些命令通常为可执行文件或者可执行文件的链接
5、etc的配置文件包含系统和软件。
6、var包含系统中服务器数据、日志等
4、Linux系统根据文件的头内容来识别其类型。为了提高文件可读性您仍可以使用文件名扩展。
5、可执行文件:可以由操作系统进行加载执行的二进制代码文件。最初的可执行文件包括代码段、数据段、堆栈段和扩展段等。代码段存放了计算机的执行指令,即CPU要进行的操作指令,数据段存放了CPU要用到的数据,堆栈段则存放了与寄存器有关的信息等等。
6、GCC、the GNU Complier Collection:GNU编译工具的集合。GCC包括了预编译cpp、编译gcc。GCC的开发工具链包含:GCC、make(生成)、ld(连接)、gdb(调试)等编译过程的常用工具。GCC编译生成的可执行文件具有代码长度短、执行效率高等特点。
7、各种文件:(Linux系统文件没有后缀)
1、c文件:c文件包含每个模块的主要源代码。
2、h文件:h文件是是c文件中源代码中的函数的声明、结构体的定义、宏的定义等。
3、o文件:目标文件(二进制文件),每个文件进过编译都会形成一个目标文件,多个目标文件链接后才形成可执行文件。
4、a文件(静态库文件):将o文件打包生成的静态库,效果和使用.o文件是一样的。
5、so文件(共享库文件):形成动态库,当程序运行的时候把它映射到自己进程空间的某一处,可以用于多个进程的共享使用(位置无关的才行)。
注:在 Linux 系统上,以下命令可使名为 hello.cpp 的 C++ 程序被预处理、编译和链接,可执行代码存储在名为 hello 的可执行文件中。
g++ -o hello hello.cpp
9、Linux系统可执行文件在终端中,输入包含可执行文件的完整路径可以执行。Linux系统不是双击执行的,可以双击执行的是.desktop文件。
10、文件机构:文件呈现在用户面前的文件结构叫做文件的逻辑结构,逻辑结构分为两种:一种是记录式文件,另一种为流式文件。记录文件:由若干逻辑记录组成,每条逻辑纪录又有相同的数据项组成。流文件 就是没有结构的文件。
11、流式文件 :在C语言中对文件的记录是以字符(字节)为单位的。输入输出的数据流的开始和结束仅受程序控制而不受物理符号(如回车换行符)控制。也就是说,在输出时不以回车换行符作为记录的间隔(事实上C文件并不由记录构成)。
“流”注:
1、在流中,定义了一些处理数据的基本操作,如读取数据,写入数据等。程序员是对流进行所有操作的,而不用关心流的另一头数据的真正流向。流不但可以处理文件,还可以处理动态内存、网络数据等多种数据形式。
2、当程序需要从某个数据源读入数据的时候,就会开启一个输入流,数据源可以是文件、内存或网络等等。相反地,需要写出数据到某个数据源目的地的时候,也会开启一个输出流,这个数据源目的地也可以是文件、内存或网络。
3、Java程序中,对于数据的输入/输出操作都是以“流”的方式进行。
4、计算机中的流:通常来说计算机所说的流是指stream,往往是对一种有序连续具有方向性的数据(其单位可以是bit,byte,packet)的抽象描述!
5、流序列中的数据既可以是未经加工的原始二进制数据,也可以是经一定编码处理后符合某种格式规定的特定数据。字节和字符串。
12、系统文件有系统文件自己的文件系统,同时对于不同的磁盘分区也有可以是不同的文件系统。Linux系统常见的ext2,ext3,ext4,sysfs,rootfs,proc等文件系统。不同文件系统的管理文件的数据结构和操作不同,但是VFS(虚拟文件系统)是对系统中所有实际文件系统的抽象,用户直接与VFS交互,VFS和不同文件系统存在映射。
13、“文件系统”和“文件系统类型”不一样!一个文件系统类型下可以包括很多文件系统即很多的super_block
14、虚拟文件系统(VFS):
1、VFS数据结构(4个):
超级块(super block):一个超级块对应一个文件系统
索引节点inode:保存的其实是实际的数据的一些信息,这些信息称为“元数据”(也就是对文件属性的描述)。例如:文件大小,设备标识符,用户标识符,用户组标识符,文件模式,扩展属性,文件读取或修改的时间戳,链接数量,指向存储该内容的磁盘区块的指针,文件分类等等。inode包含文件操作的所有信息。
目录项(dentry):目录项是描述文件的逻辑属性,只存在于内存中,并没有实际对应的磁盘上的描述,更确切的说是存在于内存的目录项缓存,为了提高查找性能而设计。例如open一个文件/home/xxx/yyy.txt,那么/、home、xxx、yyy.txt都是一个目录项。目录也是一种文件(所以也存在对应的inode)。打开目录,实际上就是打开目录文件。
文件对象(file):文件对象描述的是进程已经打开的文件。因为一个文件可以被多个进程打开,所以一个文件可以存在多个文件对象。但是由于文件是唯一的,那么inode就是唯一的,目录项也是唯一的。
2、文件操作的过程
通过应用程序通过用户态函数接口,调用VFS操作接口,VFS根据不同的情况调用具体的文件系统实现算法,这些算法通过硬件操作,完成文件操作。
文件操作相关函数:
一、文件控制
1、rename
2、remove
3、chown、修改文件的所有者(UID和GID)
4、chmod、修改文件的权限
二、文件流读写控制:
一、IO操作中,最常用的一种方式就是流,也被称为IO流。流是文件打开的一种方式,是文件在内存中的组织形式中的一种抽象。
1、fopen、打开文件,获取文件流指针
2、fclose、关闭已打开文件
3、ftell、获取文件流指针当前的读写位置。运行成功,return:当前读写位置。失败返回-1
4、fseek、设置文件流的读写位置
5、feof、判断文件流指针的当前读写位置是否已到达文件尾
6、fgetc、从指定文件流中读取一个字符
7、fputc、将单个字符写入指定的文件流中
8、fgets、从指定文件流中读取一个字符串
9、fputs、将单个字符串写入指定的文件流中
10、fread、从指定文件流中读取一段数据
11、fwrite、将指定的一段数据写入指定的文件流
文件流和文件流指针(FILE*):
1、FILE结构体(struct file)包含了文件操作的基础属性,其中包含了fd(文件标识符)。操作系统在底层不是通过文件指针来查找到文件,而是通过文件标识符来查找文件。
typedef struct _iobuf FILE;
2、文件指针(file或filp)指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个I/O缓冲区和一个文件描述符。
3、文件流的分类:根据功能分为:输入流(读取文件,用于从文件读取信息) 和 输出流(写入文件,用于创建文件并向文件写入信息);根据操作内容:字符流(读取字符数组) 和 字节流(读取字节数组)
4、输入输出流是相对于内存而言的,程序运行在内存当中,文件保存在磁盘里面,如果读一个文件,从磁盘到内存当中,属于输入流。如果从程序中输出到文件中,便是内存写入到磁盘,是输出流!
5、文件流是一种节点流,它沟通程序与文件之间的数据传输。
6、在Java中,文件被抽象为File类。
7、文件的操作流主要就是四个,FileInputStream/FileOutputStream是对文件进行字节的读写。FileReader/FileWriter是字符流,它们通过中间的编码解码器操作,将字符转换成字节或者将字节转换成字符,最终对文件的操作还是落在FileInputStream/FileOutputStream这两个字节流上。
三、文件读写操作
1、open、打开指定文件,返回文件标识符
2、creat、创建新文件,相当于open(filename,O_WRONLY | O_CREAT | O_TRUNC,mode),返回文件标识符
3、mktemp、创建临时内存文件,返回指向文件名的字符串指针
4、close、关闭文件
5、read、从指定文件(根据文件标识符)中读取数据
6、write、将数据写入指定的文件中
7、lseek、修改文件的读写位置,根据文件表示符确定文件,在根据offset偏移量和基准位置whence(文件起始位置、文件当前读写位置、文件的末尾位置)确定读写位置。
8、pipe、创建无名通道,实现一个进程(filedes[1])写,另一个进程(filedes[0])读,数据组织和队列类似(FIFO)
9、mkfifo、创建有名管道,该管道被看作是一个文件!
文件标识符:
1、文件标识符非常独特,并不是指针,其变量类型就是大家非常常用的int,从最低的空位开始,依次递增。
2、PCB中有文件描述符表(struct files_struct)。文件描述符就是这个表的索引。
3、已打开的文件在内核中用file结构体表,文件描述符表中的指针指向file结构体
四、目录操作
1、getcwd、获取当前工作目录(*buf、size_t size)
2、chdir、改变当前工作目录(*path)
3、opendir、打开目录
4、closedir、关闭已打开的目录
5、readdir、读取目录中一个文件的内容,并将目录流指针后移。return:dirent结构体
6、telldir、获取指定目录流当前指针位置
7、seekdir、设置指定目录流的指针位置