从零开始第一期——所有JAVA后端程序员必须充电!!!!深入理解Linux内存管理

Java之所以被程序员们吐槽为入门门槛低,就是因为JVM垃圾回收器帮助管理了JVM内存。但也有一句话,天下没有免费的午餐,吃的时候有多豪横,吃完就有多难受。你真觉得你不用管理内存?那你是想多了,作为后端程序员,JVM你得管,linux你得管,没有一样能跑的掉。

今天我们就从linux源码上走一走linux内存管理。

当然本来我是想把这期做在《细读经典》系列里去过一遍《深入理解linux虚拟内存管理》,但是限于个人精力(还有之前托更了好多东西)先做一个简化版,当然也不算简化版,算是一个比较深入的阐述吧。

前戏

在开始内存之前,我可能要说一些其他东西。

1、进程间通信

(1)管道:进程通过共享管道实现进程通信,实际上就是在物理内存空间内开辟出一段缓存空间。分有名管道和无名管道

无名管道,需要以文件的方式进行操作(读写),而操作文件,就需要文件描述符(fd)进行读写,文件描述符通过调用pipe方法得到(非open方法,一般而言通过调用open方法打开文件获取文件描述符),因为它没有文件名,就无法用open方法得到文件描述符,也正是因为它是一个没有名称的管道文件,所以叫无名管道,且只用于亲缘进程之间的通信(因为没有文件描述符,只能通过亲缘关系传递管道的文件描述符,例如通过fork子进程)。当然,这里的亲缘进程,可以是子进程,孙进程,孙子进程,子子孙孙无穷匮也。

无名管道API

//创建pipe通道, 传入fd数组,其中fd[0]为数据读管道描述符,fd[1]为数据写管道描述符
int pipe(int fd[2]);
//通过管道描述符从管道中读取数据  
ssize_t read(int fd, void * buf, size_t count);  
//通过管道描述符向管道中写入数据
ssize_t write (int fd, const void *buf, size_t count);  
//关闭通道的接口应用
int close(int fd);

有名管道,当然就是有文件名,可以用open函数打开文件,然后用该文件描述符进行管道建立并进行通信。所以很明显,有名管道既可以用于亲缘进程之间的通信,也可以用于非亲缘进程之间的通信。

有名管道API

//用于创建fifo管道的应用实现
int mkfifo (const char *__path, __mode_t __mode);
//打开FIFO管道,获取后续使用的描述符
int open(const char *pathname, int oflag,...);  
//从FIFO管道中读取数据  
ssize_t read(int fd, void * buf, size_t count);  
//向FIFO管道写入数据  
ssize_t write (int fd, const void * buf, size_t count);  
//关闭FIFO管道
int close(int fd);  
//移除FIFO管道
int unlink (const char *__name);  

管道还牵扯到单向通信和双向通信,我就不展开了,现在还用不到

(2)System V IPC(System linux V(罗马字字母)版本的Inter-Process Communication, 人话就是linux第五版本进程间通信)

管道是一种很原始的进程通信方式,早在linux设计之初就存在管道。随着linux版本升级到第五版本,提供了新的进程间通信方式,包含消息队列,信号量和共享内存

消息队列:看API比看文字清晰,msqid作为消息队列标识符,利用msgsnd发送,利用msgrcv接受消息,利用msgctl操作消息队列上的内容,提供删除操作。调用过程就不展开了

消息队列API

//创建消息队列
int msgget(key_t key, int oflg);
//从消息队列里读取数据
ssize_t msgrcv(int msqid, void *ptr, size_t length, long type, int flag);
//创建一个新的消息队列或访问一个已存在的消息队列
int msgsnd(int msqid, const void *ptr, size_t length, int flag);
//提供在一个消息队列上的各种控制操作
int msgctl(int msqid, int cmd, struct msqid_ds *buff);

共享内存:还是在物理内存上开辟一段缓存空间,不过与管道不同的是,使用共享内存是通过直接使用地址来共享读写的,而不需要经过系统调用。

共享内存API

//key程序需要提供一个参数key(非0整数),它有效地为共享内存段命名,shmget()函数成功时返回一个与key相关的共享内存标识符(非负整数),用于后续的共享内存函数。调用失败返回-1;
//size以字节为单位指定需要共享的内存容量;
//shmflg是权限标志,它的作用与open函数的mode参数一样
int shmget(key_t key, size_t size, int shmflg);
//shm_id是由shmget()函数返回的共享内存标识。
//shm_addr指定共享内存连接到当前进程中的地址位置,通常为空,表示让系统来选择共享内存的地址。
//shm_flg是一组标志位,通常为0。
void *shmat(int shm_id, const void *shm_addr, int shmflg);
//参数shmaddr是shmat()函数返回的地址指针,调用成功时返回0,失败时返回-1.
int shmdt(const void *shmaddr);
//shm_id是shmget()函数返回的共享内存标识符。
//command是要采取的操作,它可以取下面的三个值 :
//IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
//IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
//IPC_RMID:删除共享内存段
//buf是一个结构指针,它指向共享内存模式和访问权限的结构。
int shmctl(int shm_id, int command, struct shmid_ds *buf);

信号量:java信号量同理,所以比较重要,进程和线程的信号量是对所操作资源的保护。

这里当然要先说以下同步和互斥的关系,互斥是指资源访问的排他性,和访问顺序无关,同步是在互斥的基础上,实现有序访问。

信号量就是指定同一时间能够访问临界区资源的进程的数量。

信号量API看似简单,其实还是很精妙的,在JAVA里,信号量底层是AQS,AQS底层又是一些unsafe类,我之前也有文章讲unsafe类的,传送门:好文笔记——unsafe类_u014783007的博客-CSDN博客

//key:一个键值,和消息队列生成键值的方式一样
//nsems:表示信号量集合中信号量的个数
//semflg:一个位掩码
int semget(key_t key, int nsems, int semflg);
//semid:信号量集合的标识符
//semnum:信号量的序号,置0表示忽视该参数
//cmd:指定了相关控制操作
int semctl(int semid, int semnum, int cmd, .../*union semun arg*/);

2、用户态和内核态

我们之前一直在说系统调用,包括刚刚在说进程通信时也放了一大堆API,那么系统调用到底是什么呢?

一般而言,程序要不运行在用户态(用户交互),要不运行在内核态(系统调用)。

用户态切换到内核态是在程序申请外部资源(内存条,网卡,声卡,usb等等等等)时进行切换。一般而言,当程序在进行系统调用,或产生中断,或产生异常时就会从用户态向内核态进行切换。

例如读一个文件,我们会调用open创建fd,再用fd去进行write或read的API系统调用。例如我们分配内存时调用malloc,就会调用系统函数brk或mmap进行系统调用;又例如我们常听的缺页中断等等,程序中存在大量的系统调用(当然你映射到JAVA,new对象也是系统调用)。

系统调用,再linux中输入:

man syscalls

会显示所有的系统调用,这块就留个大家自己开发了。

这里我再举个例子,以多路复用IO为例,select和poll在进行系统调用的时候,需要把所有的fd从用户态拷贝到内核态进行遍历,本次调用结束之后,再把fd拷贝回来,而epoll则只需要在第一次进行系统调用的时候拷贝一次所有的fd,之后将用户关系的文件描述符的事件存放到内核的一个事件表中,这样只需要增量维护事件表,从而提升性能。当然epoll还会牵扯到mmap,红黑树(事件链表),等等,还会牵扯到LT,ET效率问题(LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只要有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。 ET:边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。从本质上讲:与LT相比,ET模型是通过减少系统调用来达到提高并行效率的)。你会发现其实系统函数是一个很有趣的部分,也是更贴近我们理论上所学习的各个模型的。

中断和异常不展开,有兴趣的同学自行脑补。

3、计算机组成原理(简化版)

//TODO

正戏

1、基本概念

2、实现过程

3、底层源码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值