什么是程序
简单来说就是一段指挥计算机运行的指令集合。
一个可执行程序是由我们人所能理解的语言所写的代码编译而成的机器码(指令)。
机器指令:
40055d: 48 83 ec 08 sub $0x8,%rsp
400561: be 04 06 40 00 mov $0x400604,%esi
400566: bf 01 00 00 00 mov $0x1,%edi
40056b: b8 00 00 00 00 mov $0x0,%eax
400570: e8 eb fe ff ff callq 400460 <__printf_chk@plt>
400575: b8 00 00 00 00 mov $0x0,%eax
40057a: 48 83 c4 08 add $0x8,%rsp
40057e: c3 retq
40057f: 90 nop
- 第一列:指令所处的内存地址16进制。程序为执行为什么会有内存地址?虚拟内存
- 第二列:机器指令(16进制)
- 第三列:机器指令对应的汇编语言。
包含
- 数据段,例如:int a = 10;
- 指令段,例如:if else while ± return等
程序运行所需要的角色
-
CPU
- 寄存器,简单理解为CPU自身所包含的一块高速内存
- Program Counter寄存器:用于存放下一个指令的内存地址。
- 栈指针Stack Pointer寄存器:用于存放当前栈帧的内存地址。
- 寄存器,简单理解为CPU自身所包含的一块高速内存
-
内存
- 内存模型:
- 栈区,向下增长,函数调用会占用一块内存,保存参数、局部变量以及返回值,函数执行完毕后自动释放。
- 栈帧,每个函数所占用的一小块内存。
- 可用空间
- 堆区,向上增长,通过程序手动申请/释放,未释放又不使用则是内存泄露。
- 数据段,保存全局变量
- 指令段,是只读的
- 栈区,向下增长,函数调用会占用一块内存,保存参数、局部变量以及返回值,函数执行完毕后自动释放。
- 32位操作系统中,进程的内存模型,所有进程均认为自己独占了4G内存,操作系统占用1G内存,自己可用3G内存,这就是通过操作系统实现的虚拟内存技术,进程所使用的地址空间,均为一个范围4G的地址,与真实物理内存无关。
- 内存模型:
-
硬盘
-
操作系统
- 一个程序,拥有CPU的内核运行模式,即可以使用全部的指令集,例如分配内存、操作外部设备、读写文件等。
- 运行机制:
- 系统调用,用户态程序使用read、write等方法需要使用硬件、查看系统信息时
- 中断,例如鼠标移动、键盘事件、硬件定时器Timer主动产生的中断等
- 错误,CPU执行机器码时遇到了错误,例如Segment fault
-
进程
- 本质上就是一个运行起来的程序
- 进程状态:
- 运行(Running)
- 就绪(Ready)
- 阻塞(Blocked)
- 进程状态切换:
- 运行 ---- 就绪,CPU小于进程数,进行进程间的切换。切换的这个过程是由操作系统通过中断去操作的
- 运行 ---- 阻塞,进行系统IO调用
- 阻塞 ---- 就绪,系统IO调用完毕
- 就绪 ---- 运行,分配到CPU运行时间,继续运行
- 进程切换:
- 进程切换实际上就是保存当前进程的上下文,然后将保存的历史的进程的上下文读取出来,例如各个寄存器的值,这样就切换了进程。
- 上下文实际上就是进程控制模块PCB,每一个进程都有一个PCB,在C中是一个结构体(某版本中定义有600行…),因此进程的切换是一个纯性能消耗的操作,在切换期间,CPU是没有办法执行任何操作的。
-
线程
- 本质上就是一个函数,共享进程的存储空间,在CPU的角度上并没有线程的概念,线程的概念是有操作系统封装的。
- 因为它实际上就是一个函数,因此线程的独占空间理论上就是栈区中的栈帧,但是对于栈帧间的访问实际上操作系统或者CPU并没有限制,因此在C中通过指针分享局部变量实际上可以在另外一个线程中修改另外一个线程栈上的数据的,这个一个风险点。
- 除了栈区外,其他所有的内存区域均是共享的。
-
协程
- 又称用户态线程,原因就是协程的调度是用户自己处理的,通过yield来暂定函数,再次调用时,会在上次暂停处继续运行。
- 大部分协程实现,协程的栈帧都是在堆上分配的,例如:GO。这样设计的原因是因为函数暂停后所包含的上下文,用户程序层面去进行保存,那么就只能在堆上分配内存,并将当前的栈帧copy进去,那么还不如直接将协程的栈帧分配在堆上。
- 这也是为什么go的协程可以开几十万上百万个,因为只要堆上内存足够,那么就可以一直开启协程。
-
多进/线程的好处:
- 多任务处理
- 充分利用多核
- 增强系统稳定性,进程是独享地址空间,因此自己崩溃不会影响到其他进程。而线程是共享进程的地址空间,好处是通信非常方便,但是会有竞争条件以及复杂的锁问题,并且如果线程崩溃会影响到进程而造成进程退出。
- fork之后的子进程的内存是几乎完全cp父进程的内存空间形成的。实际上linux系统下,创建子进程的速度是很快的,因为只是与父进程共享了内存空间,当父/子进程操作了共同的内存空间时,才会触发Copy On Write,保证父/子进程内存空间的独立性。
Linux查看系统中断等信息
Linux可以通过文件来查看进程以及内核的运行信息,文件在/proc下。
- /proc/interrupts中断统计信息
- irqbalance可以用来负载均衡CPU处理中断。
- 部分场景可能并不能提高性能,比如网卡,如果多个CPU同时处理一个TCP连接产生的中断,那么涉及到cache的一致性以及锁相关,反而会降低性能。
- irqbalance可以用来负载均衡CPU处理中断。
- pstree可以查看进程树