linux基础复习(2)

复习点:(1)linux虚拟地址空间   (2)缺页中断   (3)fork    (4)exit、_exit和return的区别    (5)并发和并行      (6)内存对齐问题

一、linux虚拟地址空间

在程序运行时,每个进程需要占用4G的内存空间。PC的物理内存也就不过4G或8G,那在多进程程序中如何分配内配那?因为多进程中每个进程时相对独立的,所以无法确定什么时候那个进程需要内存,如果不放任不管,就会导致不同进程对内存的争用。所以为了解决这个问题,操作系统在程序运行时采用了虚拟内存技术。

虚拟内存技术利用了部分内存空间和部分磁盘空间,将进程的大量数据放在磁盘空间上,然后再内存中建立一张索引表,在程序运行期间,当需要这一部分数据时,系统会缺页中断,通过索引表将磁盘中对应的数据,装入内存中。通过虚拟内存技术使得每个进程都拥有了4G的“内存”。

请求分页系统、请求分段系统和请求段页式系统都是针对虚拟内存的,通过请求实现内存与外存的通信置换。

1、虚拟内存的好处

(1)扩大了地址空间。

(2)内存保护:每个进程运行在各自的虚拟内存地址空间,互相不能干扰对方。虚拟内存地址技术还对特定的内存地址提供写保护,可以防止代码或数据被恶意纂改。

(3)公平内存分配。采用了虚拟内存技术之后,每个进程相当于有同样大小的虚拟空间。

(4)当进程通信时,可采用虚拟内存共享的方式实现。

(5)当不同的进程使用同样的代码时,比如库文件中的代码,物理内存中可以只存储一份这样的代码,不同的进程只需要把自己的的虚拟内存映射过去就好了,节省了空间。

(6)虚拟内存很适合在多道程序设计系统中使用,许多程序的片段同时保存在内存中,当一个程序等待它的一部分读入内存中,可以把cpu交给另一个进程使用。在内存中可以保留多个进程,系统并发度提高。

(7)在程序需要分配连续的内存空间时,只需要在虚拟内存空间分配连续空间,而不需要实际物理内存的连续空间,可以利用碎片。

2、虚拟内存的代价

(1)虚存的管理需要建立很多数据结构,这些数据结构要占用额外的内存。

(2)虚拟地址到物理地址的转化,增加了指令的执行时间。

(3)页面的换入换出需要磁盘的I/O,这会非常耗时。

(4)如果一页中只有部分数据,会浪费内存。

二、缺页中断

操作系统在程序运行使用了虚拟内存技术为进程分配内存空间,虚拟内存技术并没有给进程分配连续的内存空间,而是通过索引表加外存的方式为进程创建了一个虚拟内存,在程序运行时,通过页面置换的方式的使得进程感觉自己拥有一块连续的内存空间。

在使用malloc()和mmap()等内存非配函数时,并没有立即在内存中为用户分配这些空间,而是建立了进程虚拟地址空间,当进程访问这些没有映射关系的内存时,处理自动触发一个缺页中断。

缺页中断:在请求分页系统中,可以通过查询页表中的状态位来确定所要访问的页面是否存在于内存中。每当所要访问页面不在内存中,就会发射缺页中断,此时操作系统会根据页表中的外存地址,在外存中找到所缺的一页,将其调入内存。

1、中断的四个处理步骤

(1)保护CPU现场。

(2)分析中断原因。

(3)转入缺页中断处理程序进行处理。

(4)恢复CPU现场,继续执行。

2、缺页中断和一般中断的区别

(1)在指令执行期间产生和处理缺页中断信号。

(2)一条指令在执行期间,可能产生多次缺页中断。

(3)缺页中断返回是,执行产生中断的一条指令,而一般的中断返回是,执行的下一条指令。

三、fork

fork:创建一个和当前进程映像一样的进程。

fork函数的特点:一次调用,两次返回,三种返回值(父进程中返回子进程的pid,子进程中返回0,错误返回-1)。

成功调用fork会创建一个新的进程,它几乎与调用fork()的进程一模一样,这两个进程都会继续运行(在fork创建出子进程后,如果不对子进程做任何处理时,子进程会和父进程执行系统的程序。),在子进程中,成功fork()调用会返回0。在父进程fork()返回子进程的pid。如果出现错误,fork()返回-1。

fork最常见的用法是创建一个新的进程,然后使用exec()载入新的二进制映像,替换当前进程的映像。这样就可以在一个程序中去执行其他程序的功能了,linux中shell用的就是这种方式。在早期的unix系统中,实现fork时是将原进程的地址空间主页复制到了子进程的地址空间中 ,但是由于程序中使用fork时,经常都是直接调用exec去执行别的程序,所有这样就会导致复制的地址空间没有用,白白的浪费了时间和资源,于是设计者就在后来的系统中引入vfork的概念。

1、fork函数的底层实现原理

fork系统调用通过复制一个现有的进程来创建一个全新的进程。进程被存放的在一个叫做任务队列的双向循环链表中,链表中的每一项都是类型为task_struct称为进程描述符的结构,也就是PCB。(内核通过一个位置的进程标识值或PID来标识一个进程,最大默认值为32768,短整形的最大值,即系统中允许同时存在的进程的最大数目)。可以通过cat /proc/sys/kernel/pid_max查看。

当进程调用fork时,内核会做4件事:

(1)分配新的内存块和内核数据结构给子进程。

(2)将父进程部分数据结构内容拷贝至子进程。

(3)添加子进程到系统进程列表中。

(4)fork返回,开始调度器调度。

在Linux平台通过clone()系统调用实现fork(),fork()、vfork()和_clone()库函数都是根据各自需要的参数标识去调用clone(),然后clone去调用do_fork(),然后通过do_fork函数完成创建中大部分工作,do_fork函数在去调用copy_process()完成最后的工作。

2、fork为什么会调用一次返回两次?

由于在复制时,子进程复制了父进程的堆栈,所有两个进程都会停留在frok函数中,等待返回。因此fork函数会返回两次。在父进程中返回子进程的PID,在子进程中返回0(出错返回-1),这样我们就可以通过返回值来判断当前进程时父进程还是子进程。

调用fork之后,数据、堆、栈有两份,但代码段会被两个进程所共享,当父子进程中有想要修改数据或堆栈式,两个进程才会真正分裂,即COW(copy_on_write)技术----写时拷贝

COW技术

写实拷贝是一种采用了惰性优化方法来避免复制时的系统开销。它的前提很简单:如果多个进程要读取它们自己的那部分的资源的,那么复制是没有必要的。每个进程只要保存一个指向这个资源的指针就可以了,这样即节约了复制资源的时间和资源,还制造了一种每个进程独占资源的假象。如果有进程要修改自己的那份资源,那么就会复制那份资源,并把复制的那份提供给需要的进程,其他进程则继续贡献原有资源。而且在使用虚拟内存的情况下,数据是以页为单位的,所有就算进程修改了数据,也是只会复制修改的那一页而已。写时拷贝技术在内核中的实现为:只要页帧被共享,那么该页帧就不会被修改,就页帧被保护。无论父进程还是子进程在试图修改被共享的页帧时,就会产生一个异常,这时内核就把这个页帧复制到一个新的页帧并标记为可写。原来的页帧任然还是写保护的:当其他进程试图修改时,内核会检查该进程是否为该页帧的属主,如果是,就把这个页帧标记为对这个进程是可写的。

写时拷贝的好处:如果进程子从来就不需要修改资源,则不需要进行复制。惰性算法的好处就在于它们尽量推迟代价高昂的操作,直到必要的时刻才会去执行。

在调用fork()时,写时拷贝是有很大优势的。因为大量的fork()后都会跟着执行exec,那么复制整个的父进程地址空间中的内容到子进程的地址空间中是完全没有意义的,而且还会浪费的大量的时间和资源:如果子进程立刻执行一个新的二进制可执行文件的映像,它先前的地址空间就会被交换出去,写时复制可以对这种情况进行优化。

3、fork和vfork的区别?

vfork:vfork函数时在还没有COW机制时,为了应对fork函数之后直接调用exec函数而产生的。

函数特点:

(1)vfork函数用于创建一个子进程,而子进程和父进程共享地址空间,fork的子进程具有独立的地址空间。

(2)vfork函数保证子进程先运行,在它调用exec或exit函数之后才可以被调度。

注意:vfork函数创建的子进程结束时需要调用exit函数退出,如果没有调用该函数是会出现段错误的;原因是因为在函数栈上,子进程运行结束了,main的函数栈会被子进程释放了,父进程在使用时就会访问不到,所有一旦vfork出子进程,退出时需要使用exit来结束。

区别:

(1)fork函数中子进程拷贝父进程的代码段和数据段;vfork函数子进程和父进程公用代码段和数据段。

(2)fork函数中子进程和父进程的执行顺序不确定,依赖于操作系统调度;vfork函数保证子进程一定先于父进程运行。

(3)如果vfork函数中子进程依赖于父进程的进一步动作,那么就会发生死锁。

(4)fork函数有COW机制(不会修改父进程中的数据);而vfork函数没有,和父进程共享数据段和代码段(会修改父进程中的数据)。

四、exit、_exit和return的区别

exit函数:

功能:关闭所有文件,终止正在执行的进程。

exit(x):当x=0时,表示正常退出;x!=0时,表示异常退出。

_exit函数:

功能:直接使进程终止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构。

return函数:

return函数表示被调函数返回到主调函数函数中继续执行,返回时可附带返回值。

1、exit函数和_exit函数的区别

(1)exit函数关闭所有文件,终止正在执行的进程;而_exit函数直接使进程终止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构。

(2)exit函数时对_exit函数的封装的,在_exit函数的退出机制增加了一些安全机制,最后的收尾工作还是依赖于_exit函数。

(3)exit函数会在调用_exit之前检查文件的打开的情况,把文件的缓冲区中的内容写回文件中。

2、exit函数的退出过程

(1)调用atexit()注册的函数(出口函数);按ATEXIT注册时的相反顺序调用所有由它注册的函数,这使得我们可以指定程序终止时自己指向自己的清理动作。

(2)调用clearup()函数;关闭所有正在打开的流,这将导致写入所有被缓冲的输出,删除用TEMPFILE函数创建的零时文件。

(3)调用_exit函数终止进程。

3、_exit函数的退出过程

1,Any open file descriptors belonging to the process are closed(关闭进程打开的所有文件描述符。)
2,any children of the process are inherited by process 1, init(1号进程继承该进程的所有子进程(孤儿进程)。)
3,the process's parent is sent a SIGCHLD signal(给父进程发送一个SIGCHLD信号。)

4、exit函数和return函数

(1)exit函数用于结束正在运行的整个程序,它将参数返回给操作系统,把控制权交还给操作系统;而return是退出当前函数,返回函数的返回值,把控制权交给调用的函数。但在main函数中两者的效果是相同的。

(2)exit函数是系统调用级别,它表示一个进程的结束;而return是语言级别的,它表示调用堆栈的返回。

(3)在main函数结束时,会隐式的调用exit函数,所有一般的程序执行到main()结尾时,则结束主进程。exit将删除进程使用的内存空间,同时把错误信息返回给父进程。

(4)exit函数无论在程序的什么位置只要调用就会退出程序;而return只有在main函数中才会退出程序。

五、并发和并行

并发:指宏观上看起来两个程序同时运行,比如说单核CPU上的多任务。但是从微观上看两个程序的指令是交织这运行的,在单个周期内只运行了一个指令。这种方式不能提高计算机的性能。

并行:指严格物理意义上的同时运行,比如多核CPU,两个程序分别运行在两个核上,彼此之间相互不会影响,单个周期内每个程序都运行自己的指令,从本质上提高计算机的运行速度。

六、内存对齐问题

为什么需要内存对齐:

(1)平台原因(移植):不是所有的硬件平台都能访问任意地址的任意数据的;某些硬件平台只能在某些地址上取特定类型的数据,否则抛出硬件异常。

(2)性能原因:数据结构,特别是栈,应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要做两次访问(先定位,再偏移);而对齐的内存访问仅需要一次访问。

一般32位机器上的各个数据类型的存储空间:

char :1字节 ;short:2字节;int:4字节;long:4字节; float:4字节;double:8字节。

可以通过#pargma pack(n),n=1,2,4,8,16来改变系统中默认对齐的字节数,只有比数据成员的字节数小时,才会生效(32位机器下n默认为:4)。

结构体内存对齐规则:

(1)数据成员对齐规则:结构体的数据成员,第一个成员存放在offet为0的地址,以后每个数据成员的对齐按照#pragam pack指定的数值个这个数据成员自身长度中,比较小的那个的整数倍进行存放。

(2)结构体整体对齐股则:在数据成员各自对齐之后,结构体本身要进行对齐,对齐将按照#pargma pack指定的数值和结构体体中做大数据成员长度中较小的哪一个进行。

联合体内存对齐规则:

联合体的长度为联合体中长度中最大的。

#include <iostream>
using namespace std;
union A
{
	char a;
	short b;
};
struct B
{
	char a;
	int b;
	char c;
};
union C
{
	struct B b;
	char c;
};
int main()
{
	cout << sizeof(union A) << endl;
	cout << sizeof(struct B) << endl;
	cout << sizeof(union C) << endl;
	return 0;
}

结构体和联合体的区别:

(1)公用体和结构体都是在多个不同的数据类型成员组成,但在任何同一时刻,共用体中只存放了一个被选中的成员,而结构体的所有成员都存在。

(2)对于公用体的不同成员赋值,将会对其他成员重写,原来的成员的指就不存在了,而对于结构体的不同成员赋值是互不影响的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值