2024年C C++最新操作系统总结_msync,2024年最新学C C++看这就完事了

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

此外需要注意的是,线程的栈是不能动态增长的,指定了多大,用完就完了!当然了,默认情况下是和主线程保持一致,都是8Mb

内核栈

中断栈

函数调用与栈

在这里插入图片描述

寄存器与栈

每一个函数独占自己的栈帧空间。当前正在运行的函数的栈帧总是在栈顶。CPU系统提供两个特殊的寄存器用于标识位于系统栈顶端的栈帧。

(1) ESP:栈指针寄存器(extended stack pointer),其内存放着一个指针,该指针永远指向系统栈地上面-个栈帧的栈顶。

(2) EBP:基址指针寄存器(extended base pointer)-其内存放着一个指针,该指针永远指向系统栈展上面一个栈帧的底部。

函数栈帧:ESP和EBP之间的内存空间为当前栈帧.EBP标识了当前栈帧的底部.ESP 标识了当前栈帧的顶部。

在函数栈帧中,一般包含以下几类重要信息。
(1) 局部变量:为函数局部变量开辟的内存空间。
(2) 栈帧状态值:保存前栈帧的顶部和底部(实际上只保存前栈帧的底部,前栈帧的顶部可以通过堆栈平衡计算得到),用于在本帧被弹出后恢复出上一个栈帧。
(3) 函数返回地址:保存当前函数调用前的“断点”信息,也就是函数调用前的指令位置,以便在函数返回时能够恢复到函数被调用前的代码区中继续执行指令。

除了与栈相关的寄存器外,您还需要记住另一个至关重要的寄存器。

EIP:指令寄存器(Extended Instruction Pointer),其内存放着一个指针,该指针永远指向下一条等待执行的指令地址,可以说如果控制了EIP寄存器的内容,就控制了进程——我们让EIP指向哪里,CPU就会去执行哪里的指令。

函数调用步骤

函数调用大致包括以下几个步骤.

(l) 参数入栈:将参数从右向左依次压入系统栈中。
(2)返回地址入栈:将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。
(3) 代码区跳转:处理器从当前代码区跳转到被调用函数的入口处。
(4)栈帧调整:具体包括。
保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈):
将当前栈帧切换到新栈帧(将ESP值装入EBP.更新栈帧底部):
给新栈帧分配空间(把ESP减去所需空间的大小,抬高栈顶):
对于__stdcall调用约定,函数调用时用到的指令序列大致如下。
在这里插入图片描述
类似地,函数返回的步骤如下:
(1) 保存返回值:通常将函数的返回值保存在寄存器EAX中。
(2) 弹出当前栈帧,恢复上一个栈帧。
(3) 跳转:按照函数返回地址跳同母函数中继续执行。

一、文件系统

1. 文件系统的理解(EXT4,XFS,BTRFS)

文件系统主要用于控制所有程序在不使用数据时如何存储数据、如何访问数据以及有什么其它信息(元数据)和数据本身相关。

Linux 操作系统即便使用标准安装过程,在同一块磁盘上仍使用多个分区。拥有不同分区的一个主要目的就是为了在灾难发生时能获得更好的数据安全性。

EXT4:使用广泛,Linux现在默认的是广泛采用的 ext4,易用性以及广泛性。

XFS:XFS 文件系统是扩展文件系统的一个扩展,XFS 文件系统有一些缺陷,例如它不能压缩,删除大量文件时性能低下。

btrfs:有很多好用的功能,例如写复制、扩展校验、快照、清洗、自修复数据、冗余删除以及其它保证数据完整性的功能。

硬连接和软连接的区别

APUE 4.15和4.16

硬链接:

A是B的硬链接(A和B都是文件名),则A的目录项中的inode节点号与B的目录项中的inode节点号相同,即一个inode节点对应两个不同的文件名,两个文件名指向同一个文件,A和B对文件系统来说是完全平等的。如果删除了其中一个,对另外一个没有影响。每增加一个文件名,inode节点上的链接数增加一,每删除一个对应的文件名,inode节点上的链接数减一,直到为0,inode节点和对应的数据块被回收。注:文件和文件名是不同的东西,rm A删除的只是A这个文件名,而A对应的数据块(文件)只有在inode节点链接数减少为0的时候才会被系统回收。

软链接:

A是B的软链接(A和B都是文件名),A的目录项中的inode节点号与B的目录项中的inode节点号不相同,A和B指向的是两个不同的inode,继而指向两块不同的数据块。但是A的数据块中存放的只是B的路径名(可以根据这个找到B的目录项)。A和B之间是“主从”关系,如果B被删除了,A仍然存在(因为两个是不同的文件),但指向的是一个无效的链接。

区别:

硬链接实际上是为文件建一个别名,链接文件和原文件实际上是同一个文件。可以通过ls -i来查看一下,这两个文件的inode号是同一个,说明它们是同一个文件;而软链接建立的是一个指向,即链接文件内的内容是指向原文件的指针,它们是两个文件。

软链接可以跨文件系统,硬链接不可以;

软链接可以对一个不存在的文件名(filename)进行链接(当然此时如果你vi这个软链接文件,linux会自动新建一个文件名为filename的文件),硬链接不可以(其文件必须存在,inode必须存在);

软链接可以对目录进行连接,硬链接不可以。

文件读写使用的系统调用

读文件:

int fd = -1;

char filename[] = “/root/test.txt”;

fd = open(filename,O_RDONLY);

size = read(fd,buf,10);

close(fd);

写文件:

int fd = -1;

char buf[] = “boys and girls\n hi,children!”;

fd = open(filename,O_RDWR|O_APPEND);

size = write(fd,buf,strlen(buf));

close(fd);

二、IO

Linux的I/O模型介绍、过程、区别以及同步异步阻塞非阻塞的区别(超级重要)

阻塞IO:是在两个过程应用都处于阻塞状态。进程或线程调用某个函数,该函数需要满足特定条件才能向下执行。如果条件不满足,则会使调用进程或线程阻塞,让出CPU控制权,并一直持续到条件满足为止。

非阻塞IO:是应用发出IO操作后可以立刻返回,通过轮询盘判断数据是否准备好,在copy数据阶段阻塞应用。

IO多路复用:是阻塞调用select,查找可用的套接字,如果有套接字可用,那么就阻塞调用(recvfrom)完成数据的copy过程。Linux select就是这种模型,缺点是一次select会扫描所有的socket。

信号驱动IO:是应用发出SIGIO后立刻返回,内核中数据准备好后,通知应用,由应用进行阻塞recvfrom调用从内核copy数据。linux epoll就是基于事件的就绪通知方式,省去了所有socket的扫描开销。

异步IO:是应用发出aio-read后马上返回,数据准备好后,由操作系统把数据copy到应用,并通知应用数据copy完成。

阻塞:用户进程访问数据时,如果未完成IO,调用的进程一直处于等待状态,直到IO操作完成。

非阻塞:用户进程访问数据时,会马上返回一个状态值,无论是否完成,此时进程可以操作其他事情。
同步:用户进程发起IO后,进行就绪判断,轮询内核状态。

异步:用户进程发起IO后,可以做其他事情,等待内核通知。

IO多路复用

select,poll,epoll都是IO多路复用的机制。I/O多路复用就通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

select

select:是最初解决IO阻塞问题的方法。用结构体fd_set来告诉内核监听多个文件描述符,该结构体被称为描述符集。由数组来维持哪些描述符被置位了。对结构体的操作封装在三个宏定义中。通过轮寻来查找是否有描述符要被处理,如果没有返回。

存在的问题:内置数组的形式使得select的最大文件数受限与FD_SIZE;每次调用select前都要重新初始化描述符集,将fd从用户态拷贝到内核态,每次调用select后,都需要将fd从内核态拷贝到用户态;轮寻排查当文件描述符个数很多时,效率很低;

pool

poll:通过一个可变长度的数组解决了select文件描述符受限的问题。数组中元素是结构体,该结构体保存描述符的信息,每增加一个文件描述符就向数组中加入一个结构体,结构体只需要拷贝一次到内核态。poll解决了select重复初始化的问题。轮寻排查的问题未解决。

epoll

epoll:轮寻排查所有文件描述符的效率不高,使服务器并发能力受限。因此,epoll采用只返回状态发生变化的文件描述符,便解决了轮寻的瓶颈。

每次调用poll/select系统调用,操作系统都要把current(当前进程)挂到fd对应的所有设备的等待队列上,可以想象,fd多到上千的时候,这样“挂”法很费事;而每次调用epoll_wait则没有这么罗嗦,epoll只在epoll_ctl时把current挂一遍(这第一遍是免不了的)并给每个fd一个命令“好了就调回调函数”,如果设备有事件了,通过回调函数,会把fd放入rdllist,而每次调用epoll_wait就只是收集rdllist里的fd就可以了——epoll巧妙的利用回调函数,实现了更高效的事件驱动模型。

Epoll的ET模式和LT模式(ET的非阻塞)

LT:level trigger, 水平触发模式

ET:edge trigger, 边缘触发模式

相同点:都是通过epoll_wait从EPOLL等待队列读取激活事件
区别:1. LT模式读取激活事件后,如果还有未处理的数据。事件会重新放入EPOLL等待队列。2. ET模式读取激活事件,直接从EPOLL等待队列移除,不管是否有未处理的数据。

LT模式问题:如果可读或可写事件未处理,会频繁激活未处理事件

解决方法:不想处理读写事件时, 从EPOLL中移除句柄。需要处理时再加入EPOLL

ET模式问题:如果可读或可写没有全部处理,会有老数据残留。要等待新数据到来。

解决方法:1. 循环读取或写入数据,一直到EAGAIN或EWOULDBLOCK

  1. 读取或者写入数据后,通过epoll_ctl设置EPOLL_CTL_MOD,激活未处理事件。

ET模式饿死问题处理方法:

应用层维护一个list, 存储epoll_wait返回的就绪文件描述符, 然后循环处理

在list不为空时, 进行循环处理其中事件

每个文件描述符只进行一定限度的 IO 操作, 比如每次限定只读 1KB 数据, 然后继续处理其他的文件描述符

如果该文件描述符读了 1KB 没读完 (没有返回EAGIN / EWOULDBLOCK), 就继续停留在list中, 反之如果其上的 IO 操作执行完了, 就将其移除list

上一题中编程的时候有什么区别,是在边缘触发的时候要把套接字中的数据读干净,那么当有多个套接字时,在读的套接字一直不停的有数据到达,如何保证其他套接字不被饿死(面试网易游戏的时候问的一个问题,答不上来,印象贼深刻)。

三、进程

Linux进程管理

在很多情况下,进程都是动态创建并由一个动态分配的 task_struct 表示。一个例外是 init 进程本身,它总是存在并由一个静态分配的 task_struct 表示。在 ./linux/arch/i386/kernel/init_task.c 内可以找到这样的一个例子。

Linux 内所有进程的分配有两种方式。第一种方式是通过一个哈希表,由 PID 值进行哈希计算得到;第二种方式是通过双链循环表。循环表非常适合于对任务列表进行迭代。由于列表是循环的,没有头或尾;但是由于 init_task 总是存在,所以可以将其用作继续向前迭代的一个锚点

(15)fork返回值是什么?

fork函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

它可能有三种不同的返回值:

1)在父进程中,fork返回新创建子进程的进程ID;

2)在子进程中,fork返回0;

3)如果出现错误,fork返回一个负值;

守护进程、孤儿进程、僵尸进程
守护进程:

通常在系统后台运行,没有控制终端,不与前台交互,Daemon程序一般作为系统服务使用。Daemon是长时间运行的进程,通常在系统启动后就运行,在系统关闭是才结束。一般说Daemon程序在后台运行,是因为它没有控制终端,无法和前台的用户交互。Daemon程序一般都作为服务程序使用,等待客户端程序与它通信。我们把运行的Daemon程序称作守护进程。

孤儿进程:

一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。由于孤儿进程会被 init 进程收养,所以孤儿进程不会对系统造成危害。

僵尸进程:

一个子进程的进程描述符在子进程退出时不会释放,只有当父进程通过 wait() 或 waitpid() 获取了子进程信息后才会释放。如果子进程退出,而父进程并没有调用 wait() 或 waitpid(),那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。

僵尸进程通过 ps 命令显示出来的状态为 Z(zombie)。

系统所能使用的进程号是有限的,如果产生大量僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程。

要消灭系统中大量的僵尸进程,只需要将其父进程杀死,此时僵尸进程就会变成孤儿进程,从而被 init 进程所收养,这样 init 进程就会释放所有的僵尸进程所占有的资源,从而结束僵尸进程。

处理僵尸进程的两种经典方法

父进程回收法:

wait函数将使其调用者阻塞,直到其某个子进程终止。故父进程可调用wait函数回收其僵尸子进程。

init进程回收法:

上面的这种解决方案需要父进程去等待子进程,但在很多情况下,这并不合适,因为父进程也许还有其他任务要做,不能阻塞在这里。在讲述下面这种不用父进程等待就能完成回收子进程的方法之前,先请明白以下两个概念:

  1. 如果父进程先于子进程结束,那么子进程的父进程自动改为 init 进程。
  2. 如果 init 的子进程结束,则 init 进程会自动回收其子进程的资源而不是让它变成僵尸进程。

(18)进程终止的几种方式

1、main函数的自然返回,return

2、调用exit函数

3、调用_exit函数

4、调用abort函数

5、接受能导致进程终止的信号:

exit和_exit函数都是用来终止进程的。当程序执行到exit和_exit时,进程会无条件的停止剩下的所有操作,清除包括PCB在内的各种数据结构,并终止本程序的运行。

exit函数和_exit函数的最大区别在于exit函数在退出之前会检查文件的打开情况,把文件缓冲区中的内容写回文件,也就是清理I/O缓冲。

abort()是使异常程序终止,同时发送SIGABRT信号给调用进程。

四、线程

五、内存管理

虚拟内存

物理内存就是系统硬件提供的内存大小,是真正的内存。相对于物理内存,在 Linux 下还有一个虚拟内存的概念,虚拟内存是为了满足物理内存的不足而提出的策略,它是利用磁盘空间虚拟出的一块逻辑内存。用作虚拟内存的磁盘空间被称为交换空间(又称 swap 空间)。

作为物理内存的扩展,Linux 会在物理内存不足时,使用交换分区的虚拟内存,更详细地说,就是内核会将暂时不用的内存块信息写到交换空间,这样一来,物理内存得到了释放,这块内存就可以用于其他目的,当需要用到原始的内容时,这些信息会被重新从交换空间读入物理内存。

Linux 的内存管理采取的是分页存取机制,为了保证物理内存能得到充分的利用,内核会在适当的时候将物理内存中不经常使用的数据块自动交换到虚拟内存中,而将经常使用的信息保留到物理内存。

要深入了解 Linux 内存运行机制,需要知道下面提到的几个方面:

首先,Linux 系统会不时地进行页面交换操作,以保持尽可能多的空闲物理内存,即使并没有什么事情需要内存,Linux 也会交换出暂时不用的内存页面,因为这样可以大大节省等待交换所需的时间。
其次,Linux 进行页面交换是有条件的,不是所有页面在不用时都交换到虚拟内存,Linux 内核根据“最近最经常使用”算法,仅仅将一些不经常使用的页面文件交换到虚拟内存。

在Linux内核,我们使用vm_area_struct结构来表示一个虚拟内存区域,一个具体的vm_area_struct包含以下字段:

vm_start:指向这个区域的起始处。

vm_end:指向这个区域的结束处。

vm_port:描述这个区域包含的所有页的读写权限。

vm_flags:描述这个区域是否是私有的还是共享的。

vm_next:指向链表中下一个区域结构。

为了解释清楚这里说一下上图中与vm_area_struct有关联的task_strcut 和 mm_strcut。

内核系统为每个进程维护一个单独的任务结构在内核源码中就是task_strcut ,该结构中的元素包含内核运行该进程所需要的所有信息,(如PID、执行用户栈的指针、程序计数器)。

任务结构中的一个条目指向mm_struct,它描述了虚拟内存的当前状态。其中有两个字段是我们感兴趣的,pgd 和 mmap。pgd指向第一级页表的基址,mmap指向vm_area_struct的链表。

mmap内存映射原理

(一)进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
1、进程在用户空间调用库函数mmap,原型:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);

2、在当前进程的虚拟地址空间中,寻找一段空闲的满足要求的连续的虚拟地址

3、为此虚拟区分配一个vm_area_struct结构,接着对这个结构的各个域进行了初始化

4、将新建的虚拟区结构(vm_area_struct)插入进程的虚拟地址区域链表或树中

(二)调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
5、为映射分配了新的虚拟地址区域后,通过待映射的文件指针,在文件描述符表中找到对应的文件描述符,通过文件描述符,链接到内核“已打开文件集”中该文件的文件结构体(struct file),每个文件结构体维护着和这个已打开文件相关各项信息。

6、通过该文件的文件结构体,链接到file_operations模块,调用内核函数mmap,其原型为:int mmap(struct file *filp, struct vm_area_struct *vma),不同于用户空间库函数。

7、内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。

8、通过remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。

(三)进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
注:前两个阶段仅在于创建虚拟区间并完成地址映射,但是并没有将任何文件数据的拷贝至主存。真正的文件读取是当进程发起读或写操作时。

9、进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。
10、缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
11、调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中。
12、之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程。

注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

(16)什么是虚拟内存?

  1. 每个进程都有自己独立的4G内存空间,各个进程的内存空间具有类似的结构
  2. 一个新进程建立的时候,将会建立起自己的内存空间,此进程的数据,代码等从磁盘拷贝到自己的进程空间,哪些数据在哪里,都由进程控制表中的task_struct记录,task_struct中记录中一条链表,记录中内存空间的分配情况,哪些地址有数据,哪些地址无数据,哪些可读,哪些可写,都可以通过这个链表记录
  3. 每个进程已经分配的内存空间,都与对应的磁盘空间映射
  4. 每个进程的4G内存空间只是虚拟内存空间,每次访问内存空间的某个地址,都需要把地址翻译为实际物理内存地址
  5. 所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。
  6. 进程要知道哪些内存地址上的数据在物理内存上,哪些不在,还有在物理内存上的哪里,需要用页表来记录

4.页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)

  1. 当进程访问某个虚拟地址,去看页表,如果发现对应的数据不在物理内存中,则缺页异常

6.缺页异常的处理过程,就是把进程需要的数据从磁盘上拷贝到物理内存中,如果内存已经满了,没有空地方了,那就找一个页覆盖,当然如果被覆盖的页曾经被修改过,需要将此页写回磁盘

(17)Linux是如何避免内存碎片的

伙伴算法,用于管理物理内存,避免内存碎片;

高速缓存Slab层用于管理内核分配内存,避免碎片。

六、常用命令

(6) 查询进程占用CPU的命令(注意要了解到used,buf,***代表意义)

(7) linux的其他常见命令(kill,find,cp等等)

(8) shell脚本用法

(12) Linux监控网络带宽的命令,查看特定进程的占用网络资源情况命令

(18)Linux基本命令?

命令 作用

pwd 显示当前目录

rm 删除

touch 生成文件

cat 读取指定文件的内容并打印到终端输出

mkdir 新建目录make directory

file 查看文件类型

whereis,which,find 和 locate 查找

chown 改变文件所有者

df 查看磁盘容量

wc 计数工具

tr 删除一段文本信息中的某些文字。或者将其进行转换

join 连接两个文件

paste 它是在不对比数据的情况下,简单地将多个文件合并一起,以Tab隔开

Linux内核

img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

touch 生成文件

cat 读取指定文件的内容并打印到终端输出

mkdir 新建目录make directory

file 查看文件类型

whereis,which,find 和 locate 查找

chown 改变文件所有者

df 查看磁盘容量

wc 计数工具

tr 删除一段文本信息中的某些文字。或者将其进行转换

join 连接两个文件

paste 它是在不对比数据的情况下,简单地将多个文件合并一起,以Tab隔开

Linux内核

[外链图片转存中…(img-6jkqKXfl-1715563646175)]
[外链图片转存中…(img-DzjCTBQS-1715563646176)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上C C++开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取

  • 27
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值