自动分配/释放内存(auto_ptr | STL | 调用标准C++中的new/delete |
new/delete | 构造/析构 | C++ |
malloc/free | 标准C | 调用POSIX |
brk/sbrk | POSIX | 调用Linux系统接口 |
mmap/munmap | Linux | 调用内核接口 |
kmalloc/vmalloc | 内核 | 调用驱动 |
get_free_page | 驱动 | 。。。 |
程序是保存在磁盘上的可执行文件,加载到内存中被操作系统调用执行的程序叫做进程(一个程序可以被同时执行多次形成不同的进程)。
进程在内存中的分布情况叫映像,从低地址到高地址一次排列是:
练习七:在一个程序中打印各段内存的地址,然后与操作系统中的内存分配情况表比较,然后一一对应内存的分配情况
getpid()
获取进程的编号
cat /proc/xxx/maps
homework
每一个进程都有各自独立的4G字节的虚拟地址空间,在编程时使用的永远都是这4G的虚拟地址空间中的地址,永远无法直接访问物理地址。
操作系统不让程序直接访问物理内存而只能使用虚拟地址空间,一方面为了操作系统自身的安全,另一方面可以让程序使用到比物理内存更大的内存空间(把硬盘上的特殊文件与虚拟地址空间进行映射)
4G的虚拟地址空间被分为两个部分
注意:用户空间的代码不能直接访问内核空间的代码和数据,但可以通过系统调用(不是函数,但以函数形式调用)进入到内核间接与内核交换数据。
如果使用了没有映射过的虚拟内存地址,或者访问了没有权限的虚拟内存地址,就会产生段错误(非法内存访问)。
一个进程对应一个用户空间,进程一旦切换,用户空间也会发生变化,但内核空间有操作系统管理,它不会随着进程的切换而发生变化,内核空间由内和所管理的一张独立且唯一的init_mm
表进行内存映射,而用户空间表是每一个进程一张。
注意:每个进程的内存空间完全独立,不同的进程交换虚拟内存地址没有任何意义。所以进程之间不能直接进行通信。需要由内核中转协调。
虚拟内存到物理内存映射是以页为单位的(一页 = 4K)
都可以进行映射内存和取消映射(系统级的内存管理)
void *sbrk(intptr_t increment);
increment:
返回值:未分配前的内存首地址,以字节为单位
#include
#include
int main(){
int* ptr = sbrk(8);
printf("%d\n",ptr[1024]);//1024会段错误,1023不会
//因为sizeof(int)*1024 = 4K = 一页
}
int brk(void *addr);
设置未分配内存的首地址
返回0成功 -1失败
int main(){
int* end = sbrk(0);
if(0 == brk(end+2));//8字节
printf("%d\n",end[1024]);
brk(end);
}
他们背后维护着一个指针,记录着未分配内存的首地址(当前堆内存的最后一个字节的下一个位置),
他们都可以进行映射内存和取消映射(系统级内存管理),但为了方便起见,sbrk
一般用于分配内存,brk
用于释放内存
注意 :二者分配和释放的都是使用权,真正的映射工作由其他系统调用完成
练习8:计算1000以内的素数,存储到堆内存中,不要浪费内存
homework练习9:使用sbrk和brk实现顺序栈,容量无限
homework
#include
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
把虚拟内存地址与物理内存或者文件建立映射关系。
0xffffffff
#include
int main(){
int *ptr = mmap(NULL,4,PROT_READ|PORT_WRITE,MAP_PRIVATE,MAP_ANONYMOUS,0,0);
if(NULL == ptr){
printf("内存映射失败\n");
return 0;
}
*ptr = 100;
//ptr[1024] = 100不一定会段错误,因为它选择的是内存中随机的空间,还是以页为单位进行映射,后面的空间有可能已经映射过了.如果给定上个例程堆的地址,会出现段错误.
printf("%d %p\n",*ptr,ptr);//如果给定的地址非法,会返回随机地址.
printf("%d\n",munmap(ptr,4));
}
int munmap(void *addr, size_t length);
取消映射
UNIX/Linux系统的绝大部分功能都是通过系统调用实现的,比如:open/close…
UNIX/Linux把系统调用都封装成了C函数的形式,但它们并不是标准C的一部分
标准库中的函数绝大部分时间都工作在用户态,但部分时间也需要切换到内核(进行了系统调用),比如:fread/fwrite/malloc/free
我们自己所编写的代码也可使进行直接调用系统接口进入内核态(进行系统调用)比如brk/sbrk/mmap/munmap
系统调用的功能代码存在于内存中,接口定义在C库中,该接口通过系统中断实现调用,而不是函数进行的跳转
注意:内核态和用户态的相互切换都会消耗时间
time a.out
real 0m0.137s #总执行时间=user+sys+切换时间
user 0m0.092s #用户态的执行时间
sys 0m0.040s #内核态的执行时间
strace a.out
可以跟踪系统调用
在UNIX/Linux系统下,几乎所有资源都是以文件形式存在的,把操作系统的服务,功能,设备抽象成简单的文件,提供一套简单统一的访问接口,这样,程序就可以访问磁盘上的文件一样访问串口,终端,打印机,网络等功能
在大多数情况下,只需要 open/read/write/ioctl/close就可以实现对各种设备的输入,输出,设置,控制
UNIX/Linux下,几乎任何对象都可以当作特殊类型的文件,可以以文件的形式访问
open
打开或创建文件creat
创建文件close
关闭文件read
度文件write
写文件lseek
设置文件读写位置unlink
删除链接remove
删除文件一个非负的整数,表示打开的文件,由系统调用open/creat/socket返回值
为什么使用文件描述符而不是像标准库那样使用文件指针
因为记录文件相关信息的结构体存储在内核中,为了不暴内核的地址,因此文件结构指针不能直接给用户操作,内核中记录了一张表,其中一列是文件描述符,对应一列文件结构指针,文件描述符就相当于获取文件结构指针的下标
内核中已经有3个已经打开的文件描述符,他们的宏定义在unistd.h
STDIN_FILENO
stdin 0STDOUT_FILENO
stdout 1STdERR_FILENO
stderr 2#include
int main(){
char buf[25] = {};
fscanf(stdin,"%s",buf);
//fprintf(stdin,"hh\n");
fprintf(stdout,"xx\n");
fprintf(stderr,"hehe\n");
}
#include
#include
#include
int open(const char *pathname, int flags);
O_RDONLY
只读O_WRONLY
只写O_RDWR
读写O_NOCTTY
当打开的设备是终端设备文件,不要把该文件当作主控终端O_TRUNC
打开时是否清空O_APPEND
追加int open(const char *pathname, int flags, mode_t mode);
pathname
:文件的路径flags
:打开文件的权限
O_CREAT
文件不存在则创建O_EXCL
如果文件存在则创建失败mode
: 设置文件的权限
S_IRWXU
00700 user (file owner) has read, write, and execute permissionS_IRUSR
00400 user has read permissionS_IWUSR
00200 user has write permissionS_IXUSR
00100 user has execute permissionS_IRWXG
00070 group has read, write, and execute permissionS_IRGRP
00040 group has read permissionS_IWGRP
00020 group has write permissionS_IXGRP
00010 group has execute permissionS_IRWXO
00007 others have read, write, and execute permissionS_IROTH
00004 others have read permissionS_IWOTH
00002 others have write permissionS_IXOTH
00001 others have execute permission#include
#include
#include
#include
int main(){
int fd = open("hehe.txt",O_CREAT|O_EXCL,0755);
if(fd < 0){
perror("open");
return -1;
}
else{
perror("open");
printf("文件创建成功\n");
}
}
#include
ssize_t read(int fd, void *buf, size_t count);
fd
文件描述符,open函数的返回值buf
数据存储位置count
读取的字节数ssize_t write(int fd, const void *buf, size_t count);
fd
文件描述符buf
要写入的数据内存首地址count
写入的字节数转换成字符串后写入:
Student stu = {"hehe",'m',18};
char buf[256] = {};
sprintf(buf,"%s %c %hd",stu.name,stu.sex,stu.age);
write(fd,buf,strlen(buf));
读取后转换:
Student stu1 = {};
char buf[256] = {};
read(fd,buf,sizeof(buf));
sscanf(buf,"%s %c %hd",stu.name,&stu.sex,&stu.age);
printf("%s %c %hd\n",stu1.mane,stu1.sex,stu1.age);