系统调用
常用的系统调用
cout,printf都是最常用的系统调用,因为用户输出使用到了显示器
系统调用接口
站在开发者的角度,可以直接调用系统接口,将系统接口封装成各种各样的函数,打包成函数库
,所有开发者直接调用库函数
进程
什么叫做进程?
进程=可执行程序+内核数据结构(PCB),也是管理操作系统内任务的一种方式
进程vs程序
将一个程序编译好了放在了磁盘上就是程序,将一个可执行文件从磁盘加载到内存里就是进程,操作系统可能会同时存在非常多的“进程“,操作系统要不要管理所有的进程呢?如何管理?先描述,再组织。操作系统是用c语言写的,要描述的肯定是进程
PCB
首先操作系统先将硬盘上的数据拷贝到内存中,然后再创建pcb链表,将他们一个一个链接起来,形成了pcb链表,cpu查看哪个pcb的优先级高,cpu就讲那个所对应的文件放入到cpu中,如果不需要该进程了,则销毁从硬盘中拷贝到内存的数据,再将该进程所对应的pcb给删除,所以对进程的管理就变成了对链表的增删改查
状态:状态决定要干什么
linux中,进程PCB具体是strcut task_struct(),这里面包含了进程的各种属性,struct dlist list。
strcut task_struct(){
dlist list//可以在列表里
dlist queue//这个进程也可以在队列中
也可以在其他各种结构中
}
pcb可能在各种结构里
task_struct核心字段
pid
pid 进程(任务)对应的标识符:pid(process id)
查看进程的第一种方式:ps ajx
也就是说我们所运行的软件,程序,指令都是进程
查看进程的第二种方式proc
linux中一切皆文件,所以进程也有相应的映射文件,在proc这个文件里面,动态的进程根据进程的进程号可以显示在当前文件夹中,当进程结束时,该进程号就会销毁,也就是查不到该进程号
进程的工作目录
默认情况下,进程启动所处的路径就是当前路径。
当我们写入一个文件时,总是默认写到当前的路径下
也可以调用chdir函数去修改目录,我们将路径更改到该路径下后gcc会发现109.txt在该目录下创建了,
我们在pcb中也可以查到该进程所对应的工作路径,由此可知pcb中记录了该进程的工作路径
fork
先说结论:fork进程是创建一个子进程,该子进程在函数调用前不创建,只执行父进程,但是在函数调用后父子进程都要执行。
fork原理
1.fork干了什么事情?
一个文件的使用必须从磁盘上加载到内存,那么就会产生相应的pcb,而fork创建了一个子进程,该进程会创建一个自己的pcb,pcb中的内容会写实拷贝父进程的内容,该进程的父进程则是原来被写实拷贝,子进程则返回0,由于该子进程是由父进程创建来而非从磁盘加载来,则需要拷贝父进程中的代码过来,他们的代码是共享的,但是相互独立,各自持有一份,父进程kill了,子进程不会kill,同样子进程kill了,父进程不会kill。
2.fork为什么会有两个返回值
fork让父进程分流了,则出现了两个pcb,各自执行自己的语句,最后会return两次,所以产生两个返回值 。
3.为什么fork的父进程返回子进程的pid,子进程返回0?
因为父进程需要表示到子进程的pid才能找到子进程,而子进程不需要标记到父进程,父和子的关系相当于1:n,子只有一个父,所以是唯一的标识,而父不一定只有一个子,所以子需要有唯一标识。
4.父子谁先运行?
不确定的,是根据调度细节来决定的,
kill -9
杀死进程号,结束该命令
ppid(父进程)
在linux中,登陆之后,父进程就一直保持不变了,所以在linux中父进程一般是命令行解释器,也就是bash,我们平常创建进程的方式有命令行中直接启动,通过代码创建进程,启动进程,本质就是创建进程,我们启动命令行就是创建bash的子进程。
通过getppid调用父进程,通过getpid调用进程
父进程在登陆后都是相同的,但是同一个程序的进程可能会每次调用的时候进程号不一样了
操作系统上的进程状态
就是pcb的一个字段,就是pcb的一个变量,int status,也就是pcb->status=new。
本质:
更改pcb status整数变量
将pcb连入不同的队列中
运行状态
内存中的struct runqueue中有num,pcb *head,只要在运行队列中的进程,他的状态都是运行状态,代表了已经准备好了,可以随时被调度,每一个cpu在系统层面上都为维护一个运行队列
阻塞状态
假设我们使用cin函数,那么我们编译后不输入任何东西,那么就卡在了那里,就是被阻塞了。为什么卡住了?pcb没在运行队列中,状态不是在runnning,cpu不调度你的进程了。在操作系统中会存在非常多的队列,运行队列,等待队列,设备也有自己的队列。
挂起状态
如果一个进程当前阻塞了,这个进程在它所等待的资源没有就绪的时候,该进程是无法被调度的,如果此时,os内存不足了怎么办?
将内存的数据进行置换到外设,针对所有阻塞进程。swap分区,os的数据会交换到这里,当进程被os调度,曾经被置换过去的进程代码和数据,又被重新加载进来。以上这些操作全是用操作系统自动运行
linux中的进程状态
linux中的进程状态有特定字母去表示
R(running)
一个进程在运行队列中,在一定意义上称为就绪或者运行状态
S(sleeping)
休眠状态,浅度睡眠可以被终止,会对外部的信号做出响应,比如等待输入就会进入休眠,也就是阻塞状态
D(disk sleep)
也是休眠状态,称为深度睡眠,专门针对磁盘来设计的,不可以被杀掉,操作系统也没资格杀掉该资源,针对i/o状态的休眠,也是一种阻塞状态
T(stopped)
在软件访问资源的时候,我们进行暂停,不让进程进行访问
t(tracing stop)
debug的时候,追踪进程,遇到断点,就暂停了
X(dead)
任何进程都会有死亡状态,一个进程死亡状态有很多预处理,当进程退出时,将pcb和自己的代码和数据在内存中释放掉
为什么要创建进程?一定是要完成某种任务,进程在退出的时候要有一些退出信息,表明自己把任务完成的怎么样,该信息会记录在pcb中,也就是有了退出信息,比如main函数最后会有return 0,当一个函数需要退出的时候,会将main函数的return 0记录在pcb中,也就是将退出进程写到pcb中,可以允许进程的代码和数据被释放,但是不能允许pcb被立即释放,父进程需要读取进程的pcb退出信息,读取了子进程的原因。
Z(zombie)
进程退出了,父进程还没有被父进程和os读取,os还必须维护退出进程的pcb结构,该进程算退出了,属于僵尸状态。父进程或者os读取后,pcb先被修改成x,才会被释放。如果父进程或者os一直不回收,pcb就一直会存在,也就是内存泄漏
孤儿进程
父进程退出了,子进程还在就叫孤儿进程,此时子进程就要被1号进程给领养,也就操作系统(initd/sysemd)
defunct代表了进程已经死亡
进程的优先级
优先级存在的本质就是因为资源不足,本质就是得到某种资源的先后顺序。优先级在在pcb中映射就是int字段priority,数值越小,优先级越高。linux进程中优先级范围一般在60-99,默认情况下是80。
nice值
linux是支持动态优先级调整,但是不允许直接改优先级数字,所以pcb中会存在nice值:进程优先级的修正数字,pri(new)=pri(old)+nice。
nice值调整最小是-20,最大是19。超过按-20/19处理。优先级越高得到资源就越强,os调度的时候,应该均衡的让每个进程都得到调度,如果进程将自己的优先级调的很高,那么优先级较低的进程就会得不到cpu资源,也就是进程饥饿问题
指令top->r->pid->nice值->修改成功
并行
多个进程在多个cpu下分别,同时运行,叫做并行
并发
多个进程在一个cpu下采用进程切换的方式,在一段时间内让多个进程同时推进,就叫做并发。
每个进程不是占有cpu一直运行,每隔一段时间就自动从cpu上剥离下来。linux中cpu资源的争夺是基于时间片轮转的抢占式内核。
栈临时变量
cpu中只有一套寄存器,但是有多套寄存器数据。将cpu中的数据保存到pcb中,本质上是把cpu中的内容保存到内存中。
进程切换
每次切换的时候eax,eip等寄存器数据都保存到pcb中,需要用的时候再去取数据,用完再拷贝回去,这样就形成了并发的进程切换
linux2.6调度队列原理
在2.6的runqueue中有两个进程,一个是活跃进程(active),一个是过期进程(expired),分别用两个指针指向这两个queue。活跃进程代表已经准备好并且跑的,过期是代表等待跑的,优先级有60-99,在queue中映射到下标100-139,后面来的全都添加到expired中,不再加入active。等active全部处理完,交换expired和active的指针,其中bitmap代表位图,分别映射100-139中为不为空,如果为空则跳过,这样的好处是加快了queue的遍历速度。
命令行参数
命令行参数是支持各种指令识别的命令行选项的设置
命令行参数也是父进程,你所有的环境变量都是从bash那边拿过来的
main函数中的命令行参数
main函数中的命令行参数最多有三个,其中第三个命令行参数就是环境变量env,这是一个数组指针,指向最后一个
环境变量
系统中会存在大量的环境变量,每一个环境变量会用来完成特定的系统功能,任何的指令都是环境变量
路径设置
追加路径
在
安装程序和卸载程序本质上就是简单的拷贝
当我们登陆的时候输入用户名密码后认证,根据用户名初始化HOME目录,此时所处的路径就是当前用户家目录
env
查看bash当前所有的环境变量
环境变量参数
getenv()
获得该环境变量存放的地址
局部变量
全局变量
每一个环境变量最初都来在操作系统,操作系统再给bash,所以环境变量通常有全局属性。