计算机相关问题整理
文章目录
前言
介绍内存分区、如何调整栈大小、查询内核、相关寄存器、寻址方式、进程线程协程的区别、用户态与内核态、写时拷贝
一、程序运行时分为哪几个区
1. 在程序运行时,内存被划分为以下几个区域:
在程序运行时,通常会将内存划分为几个不同的区域。这些区域包括:
-
代码区:也称为文本区,存储程序的机器代码,通常是只读的。
-
数据区:也称为静态区或全局区,存储全局变量、静态变量和常量等数据。
-
堆区:用于动态分配内存,程序员可以通过new、malloc等函数从堆中分配内存。
-
栈区:用于存储函数的参数值、局部变量以及返回地址等信息,由编译器自动分配和释放。
注:补充内容见:‘数据存储模式与验证方法 & …’’该文章
不同操作系统和编译器对这些区域的命名和实现可能会略有差异。
2.一个典型的C程序在Linux系统下各个区域的大小分配:
- 代码区:通常占用整个可执行文件的一部分。
- 数据区:通常包括全局变量和静态变量,大小由编译器在编译时确定。
- 堆区:在程序运行时动态分配,大小由程序员手动申请和释放。
- 栈区:在程序运行时自动分配,大小通常在几MB到几十MB之间,具体取决于操作系统和编译器的实现。
3.栈在window、Linux下分别多大:
1.栈是操作系统用于支持函数调用、分配局部变量和传递参数的一种数据结构。在Windows和Linux下,栈的大小是可以配置的,通常由编译器设置默认值,也可以手动设置。
2.在Windows中,栈的默认大小为1MB.
3.而在Linux中,栈的默认大小为8MB.
二、如何使用 gcc g++编译程序调整栈的大小:
使用 gcc 和 g++ 编译程序时可以调整栈的大小。
在 Linux 系统下,可以使用 ulimit 命令来设置栈的大小,例如:
ulimit -s unlimited
这个命令将栈的大小设置为无限制。
也可以将 unlimited 替换为具体的值,例如:
ulimit -s 1024
这个命令将栈的大小设置为 1024KB。
在程序中,也可以使用 setrlimit 函数来设置栈的大小,例如:
#include <sys/resource.h>
int main() {
struct rlimit rl;
getrlimit(RLIMIT_STACK, &rl);
rl.rlim_cur = 1024 * 1024;
setrlimit(RLIMIT_STACK, &rl);
// ...
return 0;
}
这个程序将栈的大小设置为 1024KB。
另外,在编译程序时也可以使用 -Wl,-stack_size 参数来设置栈的大小,例如:
g++ -Wl,-stack_size,0x1000000 main.cpp -o main
这个命令将栈的大小设置为 16MB。
三、Linux系统下查ubuntu内核:
在Linux系统中,Ubuntu内核是由Linux内核构建而成。
- 打开终端,在命令行中输入以下命令查看当前系统使用的内核版本:
uname -a
该命令会输出当前系统的内核版本号以及其他信息。
- 如果想查看系统中所有安装的内核版本,可以使用以下命令:
dpkg --list | grep linux-image
该命令会列出系统中所有安装的内核版本。
- 如果需要在Ubuntu系统中安装新的内核版本,可以使用以下命令:
sudo apt-get install linux-image-<version>
其中<version>
为需要安装的内核版本号。
- 如果需要卸载某个内核版本,可以使用以下命令:
sudo apt-get remove linux-image-<version>
其中<version>
为需要卸载的内核版本号。
四、CPU内相关寄存器:
1.EAX,EBX,ECX, EDX寄存器
EAX,EBX, ECX, EDX是x86架构CPU中的寄存器,主要用于函数调用和堆栈操作。它们可以用于存储数据或执行算术和逻辑操作。
(1)EAX寄存器(累加器寄存器):通常用于存储函数返回值、算术运算的结果以及一些临时变量等。此外,eax还可用于存储指针地址和内存偏移量等。
(2)EBX寄存器(基址寄存器):通常用于存储内存地址、数组的基地址和指向数据段中变量的指针等。
(3)ECX寄存器(计数器寄存器):通常用于循环计数器和字符串操作等,例如,使用ecx来计算字符串长度。
(4)EDX寄存器(数据寄存器):通常用于存储I/O端口的地址和一些临时变量等。
2.EIP,ESP,EBP寄存器
(1) EIP寄存器:EIP 寄存器存储下一条指令的地址。当 CPU 执行完一条指令后,EIP 寄存器会自动加上该指令的长度,以获取下一条指令的地址。EIP 寄存器是 CPU 执行程序的关键所在,也是程序计数器(PC)的一种。
(2) ESP寄存器:ESP 寄存器指向堆栈(stack)的栈顶,堆栈是一个后进先出(LIFO)的数据结构,通常用于存储函数调用的参数、局部变量和返回值等。当函数调用时,ESP 寄存器会减少,以为新的函数调用留出空间。当函数返回时,ESP 寄存器会增加,以释放先前分配的空间。ESP 寄存器的值与堆栈指针(stack pointer)相同。
(3)EBP寄存器:EBP 寄存器通常用于指向当前函数的堆栈帧(stack frame)的底部。堆栈帧是一个包含局部变量、参数和返回地址等信息的区域。EBP 寄存器可以让程序员更方便地访问堆栈帧中的局部变量和参数,因为它提供了一个固定的参照点。
3.ESI,EDI寄存器
ESI和EDI是x86架构CPU的寄存器,它们分别代表了源索引和目的索引。它们的主要作用是在数据传输或移动时提供内存寻址方式。
(1)ESI寄存器:通常用于存放数据源地址,例如,当需要将一个字符串从内存中复制到另一个内存地址时,esi寄存器可以用来存放源字符串的地址。同样地,当需要从一个文件中读取数据时,esi寄存器也可以用来存放文件读取位置的地址。
(2)EDI寄存器:通常用于存放数据目的地址,例如,当需要将一个字符串从一个内存地址复制到另一个内存地址时,edi寄存器可以用来存放目标字符串的地址。同样地,当需要向一个文件中写入数据时,edi寄存器也可以用来存放文件写入位置的地址。
总之,ESI和EDI寄存器在程序中经常用于进行内存寻址和数据传输操作。在汇编语言中,它们通常用来作为寻址方式的基址和变址寄存器。
五、汇编语言指令
MOV,LEA,ADD,SUB,MUL,DIV
用于数据的存储、计算和转移。
1.MOV
将数据从一个位置复制到另一个位置,即数据传送指令。
2. LEA
将一个有效地址传送到目的操作数中,即有效地址计算指令。
3. ADD
将两个操作数相加,并将结果存放在目的操作数中。
4. SUB
将两个操作数相减,并将结果存放在目的操作数中。
5. MUL
将两个操作数相乘,并将结果存放在目的操作数中。
6. DIV
将目标操作数除以源操作数,并将结果存放在目的操作数中。
六、寻址方式:
1.立即数寻址:
它指的是直接将数据作为指令的一部分来访问内存,而不是通过内存地址来访问内存。在指令中直接包含数据可以提高程序的执行效率,因为不需要再访问内存来获取数据。
MOV EAX 0ah
mov eax 0ah 是一条x86汇编语言指令,它的作用是将立即数0ah(十进制数值为10)存储到eax寄存器中。
eax是x86架构中的一个通用寄存器,它可以用于存储整数类型的数据。
在这条指令中,0ah被视为立即数,也就是直接编码在指令中的常数值。
执行该指令后,eax寄存器中就会存储值为10的整数。
2.寄存器直接寻址:
它使用CPU中的寄存器来进行地址寻址,而不是通过内存来访问数据。在寄存器直接寻址中,CPU直接将指令中的寄存器地址作为操作数,从而直接访问到对应的寄存器中的数据。这种方式非常高效,因为CPU可以在一个时钟周期内直接访问到寄存器中的数据,而不需要额外的内存访问操作。
MOV EAX EBX;
MOV EAX EBX是x86汇编语言中的一条指令,用于将EBX寄存器中的值移动到EAX寄存器中。
在x86汇编中,MOV指令被用于将数据从一个位置复制到另一个位置,
常用于数据传输和寄存器之间的数据交换。
3.间接寻址:
它通常用于处理指针或引用类型的变量。在间接寻址中,指令所操作的不是一个变量本身,而是指向该变量的指针或引用。这个指针或引用被称为间接寻址操作数。
在程序中,我们可以使用指针来访问存储在内存中的数据。当我们需要访问一个指针所指向的数据时,就需要使用间接寻址。通过间接寻址,我们可以把对指针的操作转换为对其所指向的数据的操作。
MOV [EBX],EAX;
MOV [EBX],EAX;是一条汇编指令,它的作用是将eax寄存器中的值存储到ebx寄存器所指向的内存地址中。
其中方括号表示存储到内存地址的意思,这个内存地址是由ebx寄存器存储的。
这条指令在汇编语言中非常常见,通常用于将一个数据存储到内存中,以便之后进行读取或修改。
4.寄存器变址寻址:
它使用一个寄存器来计算内存地址。在计算内存地址时,寄存器中的值会被加到基地址上,从而得到最终的内存地址。这种寻址方式常用于数组和循环等操作中,可以提高程序的效率。
MOV [EBX+i],EAX;
MOV [EBX+i], EAX 是一条汇编指令
它的作用是将EAX寄存器中的值存储到以EBX寄存器的值加上i为地址的内存单元中。
其中EBX和EAX都是x86 CPU架构中的寄存器,i是一个常数或者变量,表示偏移量。
这条指令通常被用于对内存的读写操作,可以实现数据的传输、复制等功能。
需要注意的是,此指令中的方括号表示间接寻址,即从EBX+i的地址中取出数据或者将数据存储到该地址中。
七、进程、线程、协程:
1.进程和线程
1.1进程和线程两个基本概念:
进程、线程:用来实现并发执行的方式
进程:一个正在运行的程序
线程:进程内部的一条执行路径(序列)
进程是操作系统中的一个执行单位,它拥有独立的内存空间和系统资源。
每个进程都是由一个或多个线程组成的,它们共享进程的资源。
进程之间相互独立,拥有自己的地址空间和系统资源,通过进程间通信(IPC)来进行数据交换。
线程是进程中的一个执行流,是CPU调度的最小单位。
一个进程可以包含多个线程,它们共享进程的资源,包括内存空间、文件描述符等。
线程之间可以直接访问共享内存,因此线程间通信更加方便快捷。
1.2进程与线程区别如下:
- 调度:进程是由操作系统进行调度和分配资源的基本单位;而线程是CPU调度的最小单位。线程的切换比进程的切换开销更小。
- 资源占用:每个进程都有独立的内存空间和系统资源;而线程共享进程的资源,包括内存空间、文件描述符等。
- 通信和同步:进程之间通信需要使用IPC机制,如管道、消息队列等;而线程之间可以直接访问共享内存,因此线程间通信更加方便快捷。
- 稳定性:一个进程崩溃不会影响其他进程;但一个线程崩溃会导致整个进程崩溃。
2.协程
协程是一种用户态的轻量级线程,也被称为协作式多任务处理。
协程能够在一个线程内实现多个任务之间的切换,使得多个任务可以像同时运行一样,
从而提高程序的并发处理能力和执行效率。协程是一种比线程更加轻量级的并发解决方案,
因为它不需要像线程那样频繁地进行上下文切换。
2.1协程的特点包括:
1. 轻量级:协程不需要像线程那样拥有自己的堆栈空间,因此创建和销毁的开销很小。
2. 高效性:协程可以在同一线程内部进行快速切换,避免了线程上下文切换的开销。
3. 灵活性:协程可以由程序自身进行调度,从而具有更高的灵活性和可控性。
4. 可靠性:协程之间不存在竞争和死锁等问题,因此具有更高的可靠性。
2.2协程与线程的区别 :
线程是由操作系统进行调度,而协程则是由程序自身进行调度。
在一个线程内部,可以通过协程库实现多个协程之间的切换。
协程能够避免线程上下文切换的开销,同时也避免了线程之间的竞争和死锁等问题。
3.进程间的切换、线程间的切换、协程间的切换
3.1进程间的切换:
当 CPU 从一个进程切换到另一个进程时,会将当前进程的状态保存在内存中,并从内存中读取要切换到的进程的状态,然后将 CPU 的控制权转移到该进程。这个过程称为进程间的切换。进程切换的开销较大,因为要保存和恢复大量的状态信息,如寄存器、内存映射表、文件描述符等。
3.2线程间的切换:
线程是进程中的执行单元,它与进程共享进程的地址空间和其他资源,但是拥有自己的栈和寄存器。线程间的切换是通过 CPU 的上下文切换来实现的。当一个线程被挂起时,它的上下文会被保存在内存中,当该线程再次运行时,它的上下文会被重新恢复。由于线程共享同一个地址空间,所以线程间切换的开销比进程间切换小很多。
3.3协程间的切换:
协程(Coroutine)也称为微线程,它是一种用户态线程,由用户程序自己控制调度。协程具有非常高的执行效率和灵活性,因为它们是在用户空间中实现的,避免了内核态和用户态之间的频繁切换。协程间的切换可以通过用户程序自己控制来实现,不需要涉及到内核调度,因此开销比线程更小。
八.用户态和内核态的切换
1.用户态、内核态基本概念
用户态是指应用程序运行时所处的状态,应用程序只能访问自己的内存空间和有限的系统资源。
而内核态是指操作系统内核运行时所处的状态,操作系统可以访问所有的内存空间和系统资源。
2.用户态、内核态切换
当应用程序需要进行特权操作时(例如打开文件、读写硬件设备等),需要向操作系统请求帮助。
此时,应用程序就会从用户态切换到内核态,操作系统会代表应用程序执行需要的操作。
完成操作后,操作系统会把控制权交还给应用程序,此时应用程序会再次切换回用户态。
用户态和内核态的切换是由CPU完成的,具体实现方式是通过CPU提供的特权级机制
(通常是Ring0和Ring3)来实现的。
当应用程序需要进行特权操作时,就需要从Ring3切换到Ring0,即从用户态切换到内核态;
当操作系统完成特权操作后,就会从Ring0切换到Ring3,即从内核态切换回用户态。
九、写时拷贝
1.写时拷贝概念及作用
写时拷贝(简称COW)是一种常用于操作系统中的技术,它可以延迟复制数据,以节约内存的使用。
当多个进程需要访问同一个内存时,写时拷贝技术会将这些进程的访问指向同一块内存区域,直到其中一个进程要修改这块内存时,COW技术才会将这块内存复制一份,然后将修改后的内存区域指向新复制的那块内存。
写时拷贝技术可以大大减少内存的开销,并且避免了不必要的内存复制和传输。
它被广泛应用于操作系统中,如在fork系统调用时,子进程会与父进程共享同一块内存,而写时拷贝技术则可以确保子进程修改内存时不会影响到父进程。
2.写时拷贝如何实现
COW技术的实现方式有多种,其中最常用的是通过页表来实现。
在Linux操作系统中,每个进程都有一个独立的页表,它记录了虚拟地址和物理地址之间的映射关系。
当进程访问某个地址时,操作系统会先在该进程的页表中查找对应的物理地址,
如果该物理地址被多个进程共享且未被修改过,则所有进程访问该地址时都会指向同一块内存;
如果某个进程要修改该地址,则操作系统会将该页从共享状态中分离出来,
然后将其复制一份,使得修改后的数据只能被当前进程访问。