环境表的概念和使用
基本概念:
环境表本事就是环境变量的集合,每个进程拥有一张独立的环境表信息,用于记录该进程相关的环境信息
环境表本质就是一个以空指针结尾的字符指针数组,其中每个指针指向一个格式为“变量名=变量值”
+的字符串,该字符指针数组的首地址保存在全局变量char **environ中,通过该全局变量可以访问所有环境变量;
相关的基本操作函数
#include<stdlib.h>
char* buf = getenv(const char *name);
返回:查找成功返回对应的变量值,没有查找到相同的环境变量名返回NULL;
功能:主要用于根据参数指定的环境变量名去搜索整个环境表;
#include <stdlib.h>
int setenv(const char *name, const char*value, int overwrite);
参 1:字符串形式的环境变量名
参 2:字符串形式的环境变量值
参 3:是否修改的标志0:不修改 !0:表示修改
返回:成功:0 失败:-1
功能:主要用于修改/增加一个环境变量,如果环境变量不存在则增加,如果环境变量已经存在
+则是否修改取决于第三个参数overwrite;
int unsetenv(const char *name);
功能:主要用于删除参数指定的环境变量,如果该环境变量不存在则函数调用成,函数表不发生改变
intunsetenv(const char *name);
返回:成功:0 失败:!0
功能:主要用于增加/修改环境变量到环境表中,如果不存在则增加,如果存在则修改,其中参数的格式为:name=value;
intclearenv(void);
返回:成功:0 失败:!0
功能:清空环境表信息,并且将记录环境变量首地址的全局变量environ也置为空指针
main函数的原型
intmain( int argc, char * argv[], char * envp[] )
参 1:记录命令行参数的个数
参 2:字符指针数组,用于记录每个命令行参数的地址信息
参 3:用于记录当前进程的环境表信息
注意:由于历史等原因,main函数中的第三个参数不一定被所有系统支持,因此建议使用全局变量environ来访问环境表
** 内存管理技术
程序:存放在磁盘/硬盘上的可执行文件
进程:运行在内存中的程序
同一个程序可以同时对应多个进程
进程中的内存划分
***
*** 代码区(Text) ——主要用于存放功能代码,函数指针指向该区域
*** (代码区)
*** 只读常量区(Text) ——主要存放字符串常量,字面值,const修饰的已经初始化的全局变量
*** const修饰的已经初始化的static局部变量
***
*** 全局区(Data) ——用于存放已经初始化的全局变量和已经初始化的static局部变量
*** (数据区)
*** BSS段(Data) ——用于存放没有初始花的全局变量和没有初始化的static局部变量
*** ——该区域会在main函数执行之前自动清零
***
*** 堆区(heap) ——主要使用malloc()/calloc()/realloc()/free()函数所操作的内存区域
*** ——该区域的内存空间由程序员手动申请和释放的
***
*** 桟区(Stack) ——主要存放非静态的局部变量
*** ——该区域的内存空间由系统自动管理
const int num1 = 3; //桟区
//strs指向栈区,strs本身在栈区
charstrs[] = "hello";
预留空间 —— 共享库/共享内存等信息
---------> 预留空间 <------- |当前环境表信息
代码区--只读常量区--全局区--BSS段--堆区---堆区----------> 桟区---桟区---- |命令行参数信息
低-------------------------------------------------------------------------------------------------->高
0 3G-1
不同字符串存放形式的比较(重点)
对于记录常量字符串的字符指针来说,指针指向的字符串内容不可以改变,但是指针的指向可以改变
对于记录常量字符串的字符数组来说,指针指向的字符穿内容可以改变,但是指针的指向不可以改变
对于记录动态内存的指针来说,指针指向的内容和指针的指向都可以改变;
8 // &pc 表示pc本身的地址,桟区
9 // pc 表示pc指向 只读常量区
10 // 将字符串"hello"首地址放在pc的内存空间中
11 char * pc = "hello";
12
13 // 将字符串"hello"复制一份放到ps内存空间内
14 // &ps 表示ps本身的地址 桟区
15 // ps 表示ps的指向 桟区
16 char ps[] = "hello";
17
18 printf("pc = %p, &pc = %p \n", pc, &pc);
19 printf("ps = %p, &ps = %p \n", ps, &ps);
20
21 // 试图修改指针的指向
22 pc = "1234"; // OK
23 //ps = "1234"; // ERROR
24
25 // 试图修改指针所指向的内容
26 //strcpy(pc, "GooD"); //ERROR
27 strcpy(pc, "Good"); //OK
28
29 printf("--------------------------------\n");
30
31 pc = (char * ) malloc( sizeof(char) * 10 );
32 strcpy(pc, "hello");
33 char * pt = pc;
34 // 试图修改指向指向 ok
35 pc = "world";
36 free(pt);
37 pt = NULL;
虚拟内存管理技术
在linux系统中,采用虚拟内存管理技术对内存管理,即每个进程都可以拥有0~4G-1的地址
+空间(虚拟的,并不是真实存在的), 由操作系统负责建立虚拟地址到真实物理内存/文件
+的映射,因此不同进程中的地址空间看起来是一样的,但是所对应的真实物理内存/文件是不一样的
其中0~3G-1之间的地址空间叫做用户空间,3G~4G-1之间的地址空间叫做内核空间,用户程序
+一般都运行在用户空间,不能直接访问内核空间,不过系统内核提供了相关的函数访问内核空间;
内存地址的基本单位基本单位是字节,而内存映射的基本大为是内存页,目前主流的操作系统
+内存页的大小是4kb(4096个字节)
段错误的由来
试图操作没有操作权限的内存空间时可能引发段错误;
试图使用没有经过映射的虚拟地址时可能引发段错误;
使用malloc申请动态内存
使用malloc申请动态内存时的注意事项:
使用malloc函数申请动态内存时,除了申请参数指定的存储区空间之外,还可能申请额外的12个
+字节(一般性原则),用于存放该动态内存的管理信息,比如:大小,是否空闲等信息;
因此以后使用malloc申请的动态的内存时,切记不要进行越界赋值,以避免对动态内存管理信息
+的破坏,从而避免错误的发生
使用malloc申请动态内存的一般性原则:
一般来说,当使用malloc函数申请比较小块的动态内存时,操作系统可能一次性映射33个内存页大
+小的内存空间,用来提高效率;
#include<unistd.h>
#include<sys/types.h>
getpid() —— 主要用于获取当前进程的编号
节点编号、进程的名称等;
cat /proc/进程编号/mapss —— 表示查看指定进程的内存映射情况,查看结果包含以下6列:
地址范围、访问权限、偏移量、设备编号、i
使用free函数释放动态内存的一般性原则:
一般来说,使用malloc函数申请比较大块的动态内存时,系统可能会映射比33个内存页还多一些
+的内存空间,而使用free函数释放动态内存时,释放多少则从映射的总量中减去多少,当所有的
+动态内存释放完毕时,系统还会保留33个内存页,用来提高效率;
内存管理相关函数:
#include <unistd.h>
int getpagesize(void) —— 主要用于获取当前系统中一个内存页的大小并返回
void *sbrk(intptr_tincrement);
功能:主要用于根据参数指定的来调整动态内存的大小,具体调整方式如果下:
increment >0:表示申请动态内存,成功返回申请到的新内存的起始地址;
increment = 0:表示获取当前所用动态内存的末尾地址
increment <0:表示释放动态内存,成功返回释放之前的末尾地址;
无论increment的取值如何,失败都是返回(void *)-1;
注意:一般来说,使用sbrk函数申请比较小块的动态内存时操作系统可能会一次映射
+1个内存页大小的内存空间,当申请的动态内存超过1个内存页时,系统会再次映
+射1个内存页大小的内存空间,当所有的动态内存都释放时,系统不会保留映射
+的内存空间,因此相对malloc函数相比,效率比较低,但是比较节省内存空间;
sbrk函数虽然能申请内存,也能释放内存,但是使用sbrk函数申请内存空间更加方便
int brk( void * addr );
功能:主要用于根据参数指定的位置调整动态内存的大小,具体的调整方式如下:
当addr > 动态内存原来的末尾位置时,表示申请动态内存
当addr = 动态内存原来的末尾位置时,表示动态内存不变
当addr < 动态内存原来的末尾位置时,表示释放动态内存
注意:虽然brk函数即能申请动态内存,也能释放动态内存,但是释放动态内存更加方便,
+因此一般情况下与sbrk函数搭配使用,sbrk函数申请内存,brk释放内存
s
#include <sys/mman.h> (暂时了解)
void * addr=mmap(void*addr,size_t length,int prot,int flags,int fd,off_t offset);
参 1:映射的起始地址 实参给NULL表示由系统内核来指定起始位置
参 2:映射的大小
参 3:映射的访问权限,不能和文件打开模式冲突
PROT_EXEC—— 可执行
PROT_READ—— 可读
PROT_WRITE—— 可写
PROT_NONE—— 不可访问
参 4:映射的操作标志
MAP_SHARED —— 共享的,映射区数据直接反应到文件
MAP_PRIVATE —— 似有的,映射区数据不会反应到文件
参 5:文件描述符
参 6:文件的偏移量
返回:成功返回映射的其实地址,失败返回MAP_FAILED,本质就是(void *)-1;
功能:主要用于建立文件/设备到虚拟地址的映射;
int munmap(void *addr, size_t length);
参 1:映射的其实地址,mmap函数的返回值
参 2:映射的长度
功能:主要用于解除文件/设备到虚拟地址的映色
内存管理函数之间的层次关系
标准c语言 —— 使用malloc函数申请,free函数释放内存
|
POSIX标准 —— 使用sbrk函数申请,brk函数释放
|
操作系统 —— 使用mmap函数建立,munmap函数接触映射
文件的管理
在linux系统中,几乎把所有的一切都看做文件,包括目录,输入输出设备
/dev/null —— 空设备文件
eg: cat /dev/null > a.txt
清空a.txt文件里的原来的内容
文件IO操作(重点)
open\read\write\lseek\close\——不带缓冲IO(每read或write一次都会调用内核中的一个系统调用)为了提高效率最好是自己定义缓冲区最佳给一个页(4096 byte)查看当前用户页大小函数( getpagesize() )
每个进程都有一个文件描述符总表(in the system kernel)
每个文件描述符总表对应多个文件描述符,每个文件描述符对应一个文件表
文件描述符(非负整数): 标准输入:0
标准输出:1
标准错误:2
......
OPEN_MAX(256) 范围:0 ~OPEN_MAX-1
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
int fd = open ( const char *path, int flag ); —— 打开或创建一个文件
int fd = open ( const char * path, int flag, mode_t mode ); —— 打开或创建一个文件
int creat(const char *pathname,mode_t mode); —— 只创建不读写
参 2: O_RDONLY —— 只读
O_WRONLY —— 只写
* O_RDWR —— 读写 三必选一
* O_CREAT —— 如果文件不存在则创建新文件,存在则打开
* O_TRUNC —— O_CREAT+O_TRUNC,打开文件时自动清空文件
O_EXCL —— O_CREAT+O_EXCT,创建文件,文件存在创建失败返回-1,不存在,则创建
** O_APPEND —— 每次追加到文件末尾,用此方法打开则lseek位置偏移量失效
参 3: 只有在O_CREAT参数在创建新文件时有用 + 0664, 其它情况忽略
返回: 成功 —— 描述符 失败——-1
描述: 主要用于打开/创建一个文件/设备
#include <unistd>
ssize_t res = read (int fd, void * buf, size_t count);
返回:成功 ——读到的字节数,若已到达文件尾,返回0;失败 —— -1
参 3: 为buf内存大小 sizeof(buf );
ssize_t res = write (int fd, const void * buf, size_t count)
返回:成功 ——真实写入的字节数,什么都没写入,返回0;失败 —— -1
参 3:为buf的真实使用大小strlen( buf );
int close ( int fd ); 成功:0 失败:-1
off_t currpos = lseek ( int fd, off_t offset, int whence );
成功:成功返回当前位置距离到文件开头位置的偏移量 SEEK_SET
失败:(off_t) -1 SEEK_CUR
SEEK_END
注意:从SEEK_SET偏移-2个距离会报错
从SEEK_END向后偏移是可以的,当把文件的都写位置调整到SEEK_END之后时还是可以写入数据的,
只是中间有一块区域空闲,该现象叫做文件的空洞现象,该区域会被计算到文件的大小之中,但是没有有效数据,获取内容时得到的是'\0';
获取一个文件大小的方式有两种:
a.使用fseek调整到文件末尾,调用ftell函数
b.使用lseek调整到文件末尾,此事函数返回值就是文件的大小