程序和进程的基本概念
程序: 是指编译好的二进制文件,在磁盘上,不占用系统资源。
包含了一系列如何创建一个进程的信息。
1.二进制格式标识:每一个程序文件的包含用于描述可执行文件格式的信息
内核利用此信息来解释文件中的其他信息。
2.机器语言指令:对程序算法进行编码
3.程序入口地址:标识程序开始执行时的起始指令位置
4.数据
5.符号表及重定位表
6.共享库和动态链接信息
7.其他信息
进程:运行着的程序,在内存中,占用系统资源(cpu、文件描述符、锁...)
进程是最基本的分配单位。进程可以看作是程序的一个实例。
单道程序设计和多道程序设计
单道程序设计:
所有进程一个一个排队执行。
如果A阻塞,B必须等待。A发生等待事件才会进入阻塞状态哦,所以此时A并没有占用CPU,那么就会导致CPU处于空闲状态。导致CPU资源的浪费(人们总是希望CPU能够永远不休息的工作)
多道程序设计:
在计算机内存中同时存放多个独立的程序,上一篇文章讲到了虚拟地址空间,我个人认为,虚拟地址空间存在极大的方便了多道程序设计,虚拟地址空间相当于中间层,避免了直接使用内存而导致进程空间的不隔离、效率低、地址的不确定等问题。
多道程序能够实现离不开时钟中断技术,这是在硬件上的机制,对于进程而言具有不可抵抗力。(CPU对于每一个进程而言是特别好吃的食物,没有哪个进程愿意主动的放弃CPU,只能降维打击)。操作系统通过中断处理函数来负责调度程序的执行。
中断处理函数:是不是就会设计到怎么去实现。这个就是我们在学校学习操作系统时学习的一系列算法了,先来先服务、时间片轮转、短进程优先等等
并行和并发
前面说到了,多道程序设计...不可避免就会涉及到并发和并行的概念
并行:
我们经常说,多核多核..其实说的就是处理器的数量,处理器即CPU。
给大家看一下吧:
8个
一个CPU在同一时刻只能被一个进程使用,但是如果有两个CPU,同时就可以执行两个进程,那么这两个进程在同一时刻被同时执行,那么就称这两个进程时并行的
给大家画个图:
并发:
我们说并行是站在多个CPU的角度,而我们讨论并发是站在一个CPU的角度。
一个时间段,有多个进程都处于从开始运行到运行完毕的状态,但是每一个时刻只有一个程序在运行。
CPU会产生很多时钟轮片分配给不同的进程。
在多道程序设计模型中,多个进程轮流使用CPU(分时复用CPU)。
形成"宏观并行,微观串行"
CPU处理速度是纳秒级别,一秒钟可以执行10亿条指令
进程控制块PCB
引入:在虚拟地址空间中,有个内核空间3G-4G(至于为什么是0-4G之前也讲过,因为32位平台下,指针占用4个字节,这么一来就限定了指针的寻址范围是0x00000000-0xFFFFFFFF 刚好对应0-4G),好像有点偏题了。我们回到内存空间,内核空间主要是冠以设备管理、进程管理、内存管理等一些内容,其中进程管理就是我们现在要关注的对象了,它是通过进程控制块也称为进程描述符来实现的,其实它就是一个结构体,结构体一个很重要的作用不就是实现数据封装吗....
所以说PCB就是一个结构体,里面包含了进程相关的一系列信息。
主要包含的信息:(理解不了就强制背下来)
进程id:唯一标识每一个进程(相当于学号),类型 pid_t,其实是int类型 (typedef int pid_t)
进程的状态:开始、就绪、阻塞、运行、终止
进程切换所需要保存的和恢复的现场信息:其实就是寄存器里的值
描述虚拟地址空间的信息
描述当前控制终端的信息
当前工作目录
umask掩码:保护文件权限
文件描述符表
信号相关信息
会话和进程组:守护进程会用到
用户ID和组ID
进程的资源上限: ulimit -a
进程常用的命令
ps aux / ajx
a:显示所有进程
u:显示进程的详细信息
x:显示没有控制终端的信息
j:显示与作用控制相关的信息
top(任务管理器)
实时显示进程动态
kill -l 显示所有信号
kill -9 进程ID 杀死某一个进程
环境变量
操作系统中指定操作系统运行环境的一些参数。char *[]数组,数组名environ,内部存储字符串,NULL作为结束标志。
你会发现里面配的是一些路径,我们在说动态库使用的时候有说过
如何使用:
需要先什么全局变量 environ
extern char ** environ;
说白了就是这些全局变量放到了environ中,需要使用的话必须先声明
#include <iostream> using namespace std; extern char ** environ; int main(void) { for(int i=0;environ[i]!=NULL;++i) { cout<<environ[i]<<endl; } return 0; }
常见的环境变量
PATH | 可执行文件的搜索路径 |
SHELL | 当前的shell |
TERM | 当前终端 |
LANG | 语言 |
HOME | 当前用户主目录的路径 |
环境变量操作函数
char * getenv(const char *name);
int setenv(const char *name,const char *value,int overwrite);
int unsetenv(const char *name);
#include <iostream> #include <stdlib.h> using namespace std; int main(void) { char * p = getenv("PATH"); cout<<p<<endl; return 0; }
进程控制
fork函数:创建一个子进程
pid_t fork(void);
失败返回 -1,成功父进程返回子进程的pid,子进程返回0
fork之后形成的父子进程关系,其实子进程只是把父进程的内容拷贝了一份。遵从"读时共享,写时复制"。这句话是说当父子进程有写操作的时候,就会单独给子进程映射一块物理内存,防止父子进程操作同一个数据而导致数据错误。注意这里说的是物理内存,不是虚拟内存,每一个进程都有一个自己的虚拟内存空间,这个是进程刚创建的时候操作系统就完成。
循环创建n个子进程
#include <unistd.h> #include <iostream> using namespace std; int main(void) { int i; for(i=0;i<3;++i) { pid_t pid = fork(); if(pid == 0) { break; } } sleep(i); if(i < 3) { printf("I am child:%d my parent:%d\n",getpid(),getppid()); } else { printf("I am parent:%d\n",getpid()); } return 0; }
如果不让 pid==0及时的break,那么子进程和父进程在下一次循环都会frok..那么创建出来的进程数目 2^n