操作系统:对计算机软硬件资源进行管理的软件
系统调用:操作系统向用户提供的用于访问内核的接口.
程序:程序就是程序员写的代码,指令集.
进程:操作系统的角度进程就是进程控制块pcb,在linux下是task_struct结构体.
进程控制块pcb:程序运行的动态描述,包含程序运行的各项信息 如标识符pid,内存指针,程序计数器, 上下文数据,进程状态,进程优先级,io信息,记账信息等.
进程状态:就绪,运行,阻塞三大类.
linux下分为 : 运行态 -R:正在运行的,以及拿到时间片就能运行的.
可中断休眠态 -S:可以被打断的阻塞状态.
不可中断休眠态 -D:顾名思义.
停止态 -T:停止运行.
僵尸态 -Z : 程序退出后等待父处理的状态 . 僵尸进程就是僵尸态的进程,退出后资源没有完全被释放的进程,是因为子进程先于父进程退出,为了保存自己的退出的返回值等待父进程处理返回值,等待过程就没完全释放资源.
避免方法:由于僵尸进程会导致资源泄漏我们要尽量避免产生僵尸进程进程等待,wait,waitpid方法.
处理 : 退出父进程,kill命令对僵尸进程没用.(kill pid)
演示:
孤儿进程: 父进程先于子进程退出,子进程就变成了孤儿进程,1号进程成为子进程的父进程.
孤儿进程不占据当前程序运行的会话,在后台运行(当前会话能打指令)
1号进程(init)是负责任的进程,所以孤儿进程退出不会成为僵尸进程,可以被kill掉
孤儿进程设置了自己的会话空间后成为守护进程(精灵进程),会话空间是问号"?",脱离了终端运行.
演示:
查看进程信息的指令:ps -ef或ps -aux,通常配合grep使用:ps -ef | grep "string" , 查找相关进程信息
创建进程: pid_t fork(),通过复制父进程创建一个新的进程(即子进程),无参. 如果是父进程则父进程得到的返回值是子进程的pid,是子进程则为0,创建失败返回-1. 在调用fork后的子进程,父进程代码和数据一模一样. 通过返回值在代码中对父进程子进程要运行的代码分流.fork后子进程版留了父进程的环境变量和当前工作目录. fork创建子进程在内核中通过调用clone实现. 具有写实拷贝机制!子进程创建后有自己的虚拟地址空间,与父进程映射同一块物理内存,但是,如果内存中数据发生改变(父进程修改或子进程修改),则为子进程针对这 一要修改的内存另开辟空间拷贝数据过去.
环境变量:保存程序运行环境参数等待变量,在进程之间传递数据(特性)
env:查看所有环境变量 set:查看环境中所有变量 echo:打印指令变量的数据.
export:设置环境变量. unset:删除环境变量.
shell中默认环境变量存放在~/.bash_profile中.
在程序中获取环境变量的接口:char *getenv(char *name); name:环境变量名称. 返回值:对应name环境变量的数据,如果找不到返回NULL.
程序地址空间:程序地址空间,实际上是操作系统为进程通过mm_struct结构体(本质就是个结构体) 描述的虚拟的地址空间,让每个进程都能访问一个独立的完整的连续的虚拟地址,经过映射之后,实现在物理内存上的离散存储,提高内存利用率,提高了内存访问控制。通过内存管理单(MMU),S实现虚拟地址到物理地址的转换.
程序中我们看到的地址都是虚拟地址,虚拟地址经过映射后存放在物理内存中.虚拟内存的管理方式:
分段式:将虚拟地址空间分为多个段(代码段,数据段等), 分段式的虚拟地址由段号和段内偏移组成.
分段式给每个进程创建一个段表,取出虚拟地址段号,通过段号找到某个段表项(段表项就是 段表中跟据段号不同 每个段号都有自己的一些信息,称为一项, 段表中的信息只有段号和物 理起始地址)根据找到的该项里的物理起始地址,加上段内偏移量得到某个变量的实际地址.
分页式:由页号和页内偏移组成 , 对应的也创建了页表 , 但是每个页号的对应的项有页号,权限位,缺页中断位,物理块起始地址.
分段式管理:将虚拟地址空间进行分段管理,对于内存利用率并没有太大提高,但是对于程序地址管理比较友好。
分页式管理:将虚拟地址空间划分成一个个小的页面进行管理,实现离散存储,提高了内存利用率,并且在页表中进行了权限管理,提高了内存访问控制.
段页式内存管理:将虚拟地址空间先进行分段管理,然后在每个段内进行分页式管理,集合了分段和分页的优点. 地址中包含:段号,段内页号,页内偏移
1.通过段号在段表中找到段表项. 2.在段表项中找到段内页表地址 3.通过段内页号在段内页表中找到页表项 . 4.通过页表项中的物理块起始地址加上页内偏移得到物理地址.
终止,退出一个进程exit()与_exit():
库函数exit与系统调用_exit的区别就在于:退出程序前是否会刷新缓冲区. 换行也会刷新文件缓冲区.
exit与return在退出程序前都会刷新缓冲区,将还没有写入文件的数据写入到文件中
_exit调用直接退出,不会刷新缓冲区,而是直接释放资源(有可能存在缓冲区中的数据丢失)
printf("hello"); sleep(3); 等待3秒后程序退出前打印;
printf("hello\n"); sleep(3); 直接打印hello,三秒后程序退出.
printf("hello"); _exit(0);不打印hello.
printf("hello\n"); _exit(0);打印hello.
printf("hello");exit(0);打印.
printf("hello\n");exit(0);打印.
return后边的数字和exit的参数status的作用:设置进程的退出码
退出码只保留低8位〔我们设置进程退出码尽量保持在0~255之间)
waitpid(-1,&status,0)阻塞等待,等子进程退出了,waitpid处理了子进程,才向下进行代码,退出的子进程不会成为僵尸进程.和wait(&status)一样:
(阻塞等待,等待子进程退出后再向下运行.)
waitpid(-1,&status,WNOHANG)不使用循环等待的话,子进程还没到退出时间,非阻塞等待就会直接向下进行代码,然后子进程再退出后就会变成僵尸进程:
(非阻塞等待,不等待子进程退出直接向下运行.没有子进程退出ret得到返回值0)
waitpid(-1,&status,WNOHANG)使用循环等待,子进程退出不产生僵尸进程.常用写法* :
值得注意的是如果,while里写的是6秒(即每6秒看子进程退出没,退出就处理没退出就再等6秒再看退出没),则此时5-6这一秒的之间内子进程将成为僵尸进程 . 6秒后再次waitpid处理子进程,就不是僵尸进程了.
通常很少替换当前进程调度的程序,而是创建子进程之后,替换子进程所运行的程序。
以前fork之后子进程与父进程干的事情相同,可以分摊压力(这种需求其实并不多),还有一种就是让子进程干其他的活,而干活逻辑都是在if(fork()==O)这个判断中完成,但是会导致代码庞大,不灵活。因此,若创建子进程之后,根据不同的工作,将子进程替换成为不同的程序,则代码模块则可以灵活很多。
常用的替换接口:
其他的替换接口:
下图是tihuan文件:
如果使用的运行参数是main默认的argv,那么 ./tihuan 算第一个参数,后面给的指令参数接着.
下图是exec_test文件.
注意:程序替换函数如果替换成功,则这个函数调用之后的代码都不会被运行,因为程序已经被替换成为新的程序,而新的程序运行完毕之后,进程就退出了(不会返回回来运行原先的程序);
所以下面的execv success是不会打印的.
2.
execl:
由于要求传入的参数和运行环境最后一个都要为NULL,所以像下面用传myargv的话在后面加一个NULL,然后env使用默认的.
或者是:int ret =execl("./tihuan","-a","-l",NULL);