Linux系统编程-进程及相关指令与函数
目录一. 进程1.1 什么是进程进程程序1.2 进程的创建过程1.3 进程的调度并发CPU任务调度算法1.4 进程的状态三态图Linux进程状态:二. 进程相关的指令2.1 ps aux2.2 ps -ef2.3 pstree2.4 kill2.5 fg三. 进程控制函数3.1 fork3.2 getpid与getppid3.3 孤儿进程3.4 僵尸进程四. 进程的退出与消亡4.1 进程的退出4.2 如何避免僵尸进程一. 进程单任务程序运行起来只能处理一件事情多任务程序运行起来可以处理多件事情1.1 什么是进程进程进程进程就是运行起来的程序需要分配CPU资源与内存多进程之间有资源的竞争进程是一个动态的过程具有生命周期进程可以实现并发(多任务)进程是操作系统分配资源的最小单位程序程序存放在硬盘的数据指令集合静态的程序运行起来可以创建多个进程1.2 进程的创建过程在创建进程时操作系统会为其分配0~4G的虚拟内存地址空间1.3 进程的调度总结一句话宏观并行微观串行宏观上多个任务同时进行微观上多个任务按照一定的顺序执行并发并发在操作系统中一个时间段内有多个进程处于已启动运行到运行结束状态之间但是同一时间点上只有一个进程在运行CPU任务调度算法1、时间片轮询2、先来先服务后来后服务3、优先级高者先服务优先级低者后服务4、短作业优先1.4 进程的状态任何操作系统的进程切换图三态图Linux操作系统的进程切换图Linux进程状态:运行态(用户运行态内核运行态) R正在被CPU任务调度所执行的进程就绪态R正在执行但是没有被CPU任务调度的进程可唤醒等待态 S也称为睡眠态阻塞等待资源的进程不可唤醒等待态 D不想被CPU任务调度打断的进程任务可设置不可唤醒等待状态暂停态 T被暂停执行过程僵尸态 Z进程执行结束资源没有被回收结束态 X进程执行结束空间被回收二. 进程相关的指令2.1 ps aux作用查看操作系统中进程相关的信息注意1号进程为init进程作用是系统的起点收养孤儿进程清理僵尸进程等USER用户PID进程ID%CPU %MEM进程对CPU内存的占有率VSZ RSS内存相关信息TTY对终端的使用(?表示没有使用终端)STAT进程状态START TIME进程开始的日期和时间COMMAND进程名称2.2 ps -ef作用查看进程信息可以查看进程ID与其父进程ID2.3 pstree作用查看进程间的创建关系 加上选项-p可以显示进程ID2.4 kill作用给指定PID的进程发送指定的信号kill -信号编号/信号名称 PID信号可以使用命令kill -l来查看信号的种类及其信号编号9号SIGKILL结束指定的进程19号SIGSTOP暂停指定的进程18号SIGCONT继续指定的进程2.5 fg作用将指定的进程由后台调至前台使用方法fg 进程编号获取进程编号指令jobs作用获取当前终端的后台进程及其编号三. 进程控制函数3.1 fork关键在于duplicating作用创建一个子进程返回值0表示在父进程中返回子进程的PID0表示当前处于子进程中0出错并且设置errno注意1、子进程完全拷贝父进程的0~3G进程地址空间(用户空间堆区栈区文本区数据区等)2、但是对内核区的拷贝不完全比如PCB中的PID号就不一样但是又拷贝了文件描述符3、父子进程之间数据不共享要想通信就得使用进程间通信方式可以使用下面一段代码来验证//int num 100;全局变量不共享 int main() { //int num 300;//局部变量也不共享 int *pnum malloc(sizeof(int));//堆区资源也不共享 if(pnum NULL){ perror(malloc()); return -1; } *pnum 600; pid_t pid fork(); if(pid 0){ perror(fork()); return -1; } else if(pid 0){ while(1){ printf(---parent: my pid is %d,my parent pid is %d, num %d\n, getpid(), getppid(), *pnum); sleep(1); } } else{ //num 200; *pnum 900; while(1){ printf(-----child: my pid is %d,my parent pid is %d, num %d\n, getpid(), getppid(), *pnum); sleep(1); } } return 0; }这里有一个小栗子问下面这个程序总共可以fork出几个进程int main() { fork() fork() || fork(); getchar(); return 0; }结果使用pstree -sp 进程ID查看可以看出总共有五个进程这里需要注意与||操作符的短路效应表达式1 表达式2 || 表达式3 这条语句如果表达式1是假那么后面是不是就都不执行了不是这样的表达式1为假只能短路掉表达式2|| 左边整体为假故表达式3仍要执行。3.2 getpid与getppid作用获取当前进程的pid与当前进程的父进程的pid3.3 孤儿进程父进程先于子进程结束则此子进程成为孤儿进程则这个子进程的父进程变成了一个系统进程在早期的操作系统中孤儿进程直接被1号进程init进程收养但是init进程的任务繁重后来又重新封装了一个进程组来回收孤儿进程孤儿进程的用处后台的守护进程可以使用下面的代码来观察孤儿进程int main() { pid_t pid fork(); if(pid 0){ printf(parent:pid is %d, child is %d\n,getpid(),pid); sleep(10);//父进程10s后死亡子进程成为孤儿进程 } else if(pid 0){ while(1){//子进程循环打印 printf(child:pid is %d, parent is %d\n,getpid(),getppid()); sleep(1); } } return 0; }结果在父进程结束后子进程的父进程成为了1495这个进程3.4 僵尸进程子进程结束后父进程没有回收子进程残留在内核空间中的资源(PCB)导致该子进程变成了僵尸进程。注子进程结束后其用户空间的所有资源默认由操作系统回收但是内核空间的PCB还残留在内核中原因PCB记录了子进程退出的原因若是正常退出则保存其退出状态若是异常退出则保存使其异常退出的原因比如信号等等父进程可以调用wait或者waitpid来获取这些信息然后彻底清除掉这个进程残留的资源。使用kill -9命令无法使僵尸进程结束只能将其父进程结束然后该子进程成为孤儿进程然后被收养和回收使用这段代码可以观察到僵尸进程这个状态int main() { pid_t pid fork(); if(pid 0){ while(1){ printf(parent:pid is %d, child is %d\n,getpid(),pid); sleep(1);//父进程一直在打印没有回收子进程则子进程变成僵尸进程 } } else if(pid 0){ printf(child:pid is %d, parent is %d\n,getpid(),getppid()); sleep(10);//子进程10s后结束 printf(child die\n); } return 0; }结果10s前10s后可以看到子进程变成了僵尸进程Z四. 进程的退出与消亡4.1 进程的退出1. 在主函数中return2.exit()库函数直接结束一个进程,结束前会刷新缓冲区3._exit(),_Exit()系统调用直接结束一个进程,结束前不会刷新缓冲4.2 如何避免僵尸进程1. 子进程结束由其父进程回收资源空间2. 让子进程成为一个孤儿进程