1.什么是虚拟地址空间?为什么需要虚拟地址空间?
虚拟地址空间是指一个程序在运行过程中所能访问的内存地址的集合。每个运行的程序都有自己的虚拟地址空间,它是一个抽象的概念,与实际的物理内存地址没有直接的对应关系。
虚拟地址可以进行:内存隔离,地址空间扩展,内存映射,内存管理。虚拟地址空间提供了一种抽象和隔离的机制,为多个程序提供独立的内存地址空间,同时也提供了灵活性和效率方面的优势。
内存隔离:虚拟地址空间可以使每个运行的程序看起来好像拥有整个计算机的内存空间,但实际上每个程序只能访问自己的虚拟地址空间。这样可以确保不同的程序之间互不干扰,提高系统的安全性和稳定性。
地址空间扩展:虚拟地址空间的大小可以远远大于物理内存的大小。通过使用虚拟地址空间,程序可以使用比实际物理内存更大的地址空间,从而能够处理更大的数据集或者运行更复杂的程序。
内存映射:虚拟地址空间允许将磁盘上的文件或其他设备映射到内存中,使得程序可以像访问内存一样访问这些资源。这种内存映射的方式可以简化文件读写操作,并且提供了高效的访问方式。
内存管理:虚拟地址空间使得操作系统能够更加灵活地管理内存。通过使用虚拟地址空间,操作系统可以将物理内存分配给不同的程序,并进行合理的内存回收,从而提高了内存的利用率。
Linux系统下用什么命令可以查看一个可执行文件的虚拟地址空间分布?请给出命令操作截图。
pmap 可执行文件 //可以进行查询
首先执行一个文件,对其进行进程号查询
pmap pid
2.什么是孤儿进程,什么是僵尸进程,什么是守护进程,三者与操作系统的关系是什么?
孤儿进程:当一个父进程结束或意外终止时,它的子进程可能还在运行。这样的子进程就会成为孤儿进程,因为它失去了父进程。通常情况下,孤儿进程会被init进程(PID为1)接管,并由init进程来管理和回收。
僵尸进程:当一个子进程结束时,父进程需要调用wait
或waitpid
等系统调用来获取子进程的终止状态信息,并释放子进程占用的系统资源。如果父进程没有及时处理子进程的终止状态,那么子进程的进程描述符仍然存在于系统中,这样的进程就会成为僵尸进程。僵尸进程不会占用CPU资源,但会占用系统资源表项。过多的僵尸进程可能会影响系统的性能。
守护进程:守护进程是在后台运行的一种特殊类型的进程,通常是在系统启动时启动,并且在系统关闭时结束。守护进程通常不会与用户交互,它们通常用于执行系统任务、监控硬件设备或执行周期性任务等。典型的守护进程包括网络服务程序和系统监控程序。
三种进程与操作系统的关系
操作系统负责管理和调度进程,包括创建新进程、终止进程、回收进程资源等。
❗对于孤儿进程,操作系统会将其交给init进程来接管。
❗操作系统也会负责回收僵尸进程的资源,当父进程调用wait/waitpid等系统调用时,操作系统就会清理僵尸进程。
❗操作系统会在启动时自动启动守护进程,并在系统关闭时结束守护进程。
Linux进程4:孤儿进程,僵尸进程(及解决方法),守护进程讲解_怎么解决孤儿进程-CSDN博客
具体可以参考这篇文章
利用什么命令可以查看指定进程的信息和状态?
先使用pgrep 可执行文件 //来查询进程的pid
ps -p pid
top
htop //这些都可以来对进程的信息和状态进行查询
3.什么时候会发生段错误?段错误发出的信号是什么?
段错误(Segmentation Fault)是一种常见的编程错误,当程序尝试访问未分配给它的内存区域时会发生。段错误通常是由以下几种情况引起的:
-
访问空指针:当程序试图通过空指针来访问内存时,会导致段错误。空指针是指没有被初始化或赋予有效地址的指针。
-
野指针:当程序使用已经释放的或者无效的指针来访问内存时,会导致段错误。
-
数组越界:当程序试图访问数组越界的元素时,会导致段错误。例如,访问一个超出数组长度的索引或者负数索引。
-
栈溢出:当程序使用过多的栈空间时,栈会溢出,导致段错误。这通常发生在递归函数调用层数过多或者局部变量占用过多栈空间的情况下。
-
内存泄漏:当程序动态分配的内存没有被正确释放,导致内存耗尽时,可能会导致段错误。
段错误发出的信号是SIGSEGV(Segmentation Violation)。当出现段错误时,操作系统会向进程发送SIGSEGV信号,该信号可用于捕获和处理段错误。
#include <stdio.h>
#include <stdlib.h>
int main() {
int *ptr;
*ptr = 10; // 试图将值赋给一个未初始化的指针所指向的地址
return 0;
}
上面的代码编译不会发生错误,但是因为在这个程序中,指针ptr没有被初始化,它包含了一个随机的内存地址。当我们尝试通过ptr来访问或修改它所指向的内存地址时,就会产生段错误Segmentation fault
4.Linux为了支持各种各样的文件系统实例,将文件系统公共部分抽象出来,统一各种不同文件系统的接口,形成了虚拟文件系统(virtual file system, vfs)。vfs涉及到的比较重要的数据结构有5个。请问是哪5个?这5个数据结构各有什么含义和作用?
Linux的虚拟文件系统(VFS)涉及到了几个重要的数据结构。以下是这五个数据结构以及它们的含义和作用:
-
super_block
:每个挂载的文件系统都对应一个super_block
结构体,它描述了整个文件系统的基本信息,如文件系统类型、块大小、根目录等。super_block
是一个全局数据结构,用于管理文件系统的整体状态。 -
inode
:inode
结构体表示一个文件或目录在文件系统中的元数据。它包含了文件或目录的权限、大小、时间戳等信息,以及指向数据块的指针。inode
可以看作是文件或目录的索引节点,用于标识和管理文件系统中的各个实体。 -
dentry
:dentry
结构体表示一个目录项,即文件系统中的一个文件名。dentry
通过指向相应的inode
来关联文件名和文件的实际数据。dentry
还包含了一些缓存和状态信息,以提高文件系统的性能。 -
file
:file
结构体表示进程打开的文件描述符。它包含了文件访问的相关信息,如文件偏移量、访问模式等。file
结构体用于管理进程与文件之间的交互,并且在内核中用于执行文件读写操作。 -
address_space
:address_space
结构体用于管理文件系统中的数据缓存。它包含了文件数据在内存中的缓存页的相关信息,以及与文件数据的读写操作相关的状态和方法。address_space
通过页缓存来提高文件访问的性能。
这些数据结构共同构成了Linux的虚拟文件系统框架,并提供了抽象接口来统一不同文件系统的操作。它们通过协作,使得用户程序可以以统一的方式访问不同类型的文件系统,为应用程序提供了方便和灵活性
用什么命令可以知道是哪个进程打开了哪个文件?
lsof
5.什么是字符设备、块设备?字符设备和块设备的根本区别是什么?主设备号和次设备号各有什么用途?
字符设备和块设备是指在Unix和类Unix系统中用于访问硬件设备的两种不同方式。
字符设备(Character Device):
- 字符设备以字节为单位进行输入和输出,它们以流的形式处理数据,如键盘和鼠标。
- 例如,串行端口、终端设备、声卡等通常被视为字符设备。
- 字符设备对数据的读写是按照它们实际产生或者接收的顺序进行的,不需要缓冲区。
块设备(Block Device):
- 块设备以固定大小的块为单位进行输入和输出,通常具有缓冲区和缓存,如硬盘和闪存设备。
- 块设备以块的形式处理数据,而不是按照数据流的形式。
- 由于块设备有缓冲区,所以它们通常能提供更高的性能。
根本区别:
- 最根本的区别在于数据传输的方式:字符设备以流的形式处理数据,而块设备以固定大小的块来处理数据。
- 另一个区别是块设备通常具有缓冲区,因此可以进行缓存和缓冲,而字符设备则没有这样的特性。
主设备号和次设备号:
- 在Unix和类Unix系统中,每个设备都由主设备号和次设备号来唯一标识。
- 主设备号用于识别设备类型,而次设备号用于识别具体的设备。
- 内核使用主设备号来确定应该使用哪个驱动程序来管理设备,次设备号用于标识具体的设备。
- 例如,对于/dev/sda1,"/dev/sda"表示主设备号,"1"表示次设备号。
总的来说,字符设备和块设备的根本区别在于数据传输的方式和是否具有缓冲区,而主设备号和次设备号用于唯一标识设备并确定设备类型和具体设备。
用什么命令可以查看内核中已有的字符设备和块设备的信息?用什么命令可以查看正在使用的有哪些中断号?
lsblk
:用于列出系统上的块设备信息,包括硬盘、SSD、闪存驱动器等。它会显示设备的名称、大小、挂载点等详细信息。
lspci
:用于列出系统上的PCI设备信息,包括一些块设备(如硬盘控制器)以及其他类型的设备。它会显示设备的供应商ID、设备ID、驱动程序信息等。
lsusb
:用于列出系统上的USB设备信息,包括一些块设备(如USB存储设备)以及其他类型的设备。它会显示设备的供应商ID、产品ID、驱动程序信息等。
cat /proc/interrupts
:此命令将显示系统中所有的中断向量及其对应的中断号、处理器、中断源等信息。这可以帮助你了解哪些中断正在被使用以及它们的分配情况。
6.CPU在执行进程时可以直接访问物理地址吗?为什么?
CPU在执行进程时不能直接访问物理地址。
不能直接访问物理地址是为了实现操作系统的虚拟内存管理和硬件保护机制。通过虚拟内存管理,操作系统可以更好地管理内存资源,提供了更高的灵活性和安全性。同时,硬件保护机制可以确保不同进程之间的内存空间得到有效隔离,从而保证系统的稳定性和安全性。
如果CPU可以直接访问物理地址,会存在以下问题:
缺乏内存保护:进程之间的地址空间无法有效隔离,一个进程可能会意外地修改另一个进程的内存数据,导致系统不稳定甚至崩溃。
效率低下:虚拟内存管理可以使得操作系统能够更好地管理内存资源,提高了内存的利用率,同时也提供了更高的灵活性,例如内存扩展等功能。
数据安全:虚拟内存管理可以帮助操作系统保护用户程序的数据安全性和隐私性,防止恶意程序非法访问其他进程的数据。
因此,为了实现更好的内存管理、系统稳定性和数据安全性,现代计算机体系结构采用了虚拟内存管理和硬件保护机制,CPU不能直接访问物理地址,而是通过虚拟地址和页表进行访问。
7.分析代码,说明内存泄漏的原因
void MyFunction(int nSize, bool mark)
{
char* p= new char[nSize];
if( mark &&!GetStringFrom(p,nSize) )
{ MessageBox("Error");
return;
}
//using the string pointed by p;
delete p;
}
在代码中,使用了 new[ ] 关键字动态分配了一块内存,但在函数结束之前没有使delete[] 来释放这块内存。
- delete p 用于释放通过 new 关键字分配的单个对象的内存。
- delete[ ] p 用于释放通过 new[ ] 关键字分配的数组对象的内存。
所以应该是最后的delete没有正确使用
8.以下代码可能存在的问题
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
char* upcase(char* string, int length);
int main(void)
{
char* buf, * newstr;
char oldstr1[] = "abcdefg";
char oldstr2[] = "xyz";
int counter;
buf = malloc(15);
strcpy(buf, "CHANGE STRINGS.");
fprintf(stdout, "%s\n", buf);
free(buf);
for (counter = 0; counter < sizeof(oldstr1); counter++)
{
putchar(oldstr1[counter]);
putchar(oldstr2[counter]);
}
newstr = upcase(oldstr1, sizeof(oldstr1));
fprintf(stdout, "NEW STRING 1:%s.\n", newstr);
free(newstr);
newstr = upcase(oldstr2, sizeof(oldstr2));
fprintf(stdout, "NEW STRING 2:%s.\n", newstr);
free(newstr);
strcpy(buf, "THE END.");
fprintf(stdout, "%s\n", buf);
free(buf);
return 0;
}
char* upcase(char* string, int length)
{
char* newstring;
char temp;
int counter;
newstring = calloc(length, sizeof(char));
if (newstring == NULL) {
fprintf(stderr, "Memory allocation failed.\n");
return NULL;
}
for (counter = 0; counter < length; counter++)
{
temp = *(string + counter);
if (temp >= 97 && temp <= 122)
*(newstring + counter) = temp - 32;
else
*(newstring + counter) = temp;
}
return newstring;
}
在程序中使用了
free
函数来释放动态分配的内存,但在某些情况下并没有进行必要的判空操作。在第一次调用free(buf)
后,程序继续尝试对已释放的指针进行操作,可能导致未定义的行为。在
main
函数中,使用malloc
分配了内存给buf
,但在程序结束前只释放了一次,应该在每次使用完动态分配的内存后都进行相应的释放。在
upcase
函数中,使用了calloc
来分配内存给newstring
,但在upcase
函数返回前未释放这块内存。这会导致内存泄漏。在
upcase
函数中,length
参数表示了字符串的长度,但在C语言中字符串是以空字符\0
结尾的,所以计算字符串长度应该使用strlen
函数而不是sizeof
操作符。在
main
函数中,for
循环中使用了sizeof(oldstr1)
和sizeof(oldstr2)
来遍历字符串,但这将包括字符串末尾的空字符在内,可能导致访问越界。如果
malloc
或calloc
分配内存失败,程序并没有进行处理,会导致空指针解引用的问题。