进程优先级
- 进程优先权是cpu分配资源的先后顺序,优先权高的进程有优先执行的权利。
- 进程优先级是为了保证操作系统比较合理的调度进程。
- 进程有限级的查看可以先在xshell的第一个界面写一个死循环,让他一直运行,然后重新开一个xshell,输入(process_status 是正在执行的死循环程序)
ps -efl | grep ./process_status
然后使用top命令查看进程优先级
- PR: 进程优先级的数值,不能直接去修改PR的值
- NI: 进程优先级的修正值,可以通过修改NI的值来修改PR的值
- PR(new) = PR(old) + NI
修改NI值的方法: top --> r --> 输入进程的pid --> 输入修改后NI的值
ps -l
- UID: 代表执行者的身份
- PID: 表示这个进程的代号
- PPID: 表示这个进程是由哪个进程衍生来的,也就是父进程的代号
- PRI: 表示这个进程被可执行的优先级,值越小越先执行
- NI: 代表这个进程的nice值
进程创建
fork
上一篇进程概念
vfork
- vfork创建出来的子进程PCB也是拷贝父进程的PCB,但是子进程的PCB指向父进程的虚拟地址空间
- 会带来父子进程调用栈混乱的问题
- vfork让子进程先执行,父进程进行进程等待,等到子进程执行完毕之后再执行
fork 和 vfork区别
fork :在内核当中以父进程的PCB为模板,拷贝父进程PCB当中的内容创建一个PCB,父子进程之间的PCB被内核使用双向链表管理起来(copy)
vfork :在内核当中以父进程的PCB为模板,拷贝父进程PCB当中的内容创建一个PCB,子进程的PCB指向父进程的虚拟地址空间,PCB被内核使用双向链表管理起来(share)。
- 使用vfork会造成一个栈混乱的问题 : 让子进程先运行,再让父进程运行。
因为如果vfork的子进程里面用return,整个程序会挂掉,而exit() 和 _exit()不会。因为在vfork中使用return ,则就意味着我们在当前的栈中调用了return,因为vfork()的父子进程是share的关系,就等于我们改变了整个栈的栈帧,然后在执行父进程的时候,出现一个诡异的栈,就会导致main函数崩溃。
return在返回时会对栈进行操作,将栈清空。然后跳转到调用函数的下一条指令。而_exit没有对栈操作。所以说vfork使用时会造成栈混乱的问题,使用vfork得用exit或者_exit返回结果。
进程终止
进程终止的本质就是让进程退出。
我们运行一段函数,会出现几种情况,一个是退出函数但是函数返回值是错误的,另一种是函数正常退出并且返回值正确,再一种是进程异常终止。
- 程序正常退出的方式: 在main函数的return 中退出(return 0 表示程序正常终止);利用库函数exit();系统功能调用函数 _exit()。
- 异常退出的方式: 解引用NULL空指针;ctrl + c;内存访问越界
echo $? //查看刚才结束进程的退出码
如果进程结束 exit(-1) 此时查看的退出码是255,因为-1在计算机中的存储是全1
exit 和 _exit的区别:(stdlib.h)
- 如果程序前有atexit()定义的退出清理函数,那么exit 会执行用户自定义的清理函数,而_exit则不会
- exit会冲刷缓存区,并且关闭流,而_exit不会
linux下刷新缓冲区的方式: 换行 \n;fflush(stdout);从main函数的return返回;调用exit函数
#include <stdio.h>
int main()
{
printf("hello world");
//fflush(stdout);
//exit(0);
sleep(5);
return 0;
}
该程序执行后的结果便是,需要先睡眠5s,待清除缓冲区后,linux下才会显示hello world
进程等待
- 进程等待的原因: 如果说我们fork创建出一个子进程,父子进程之间为抢占式执行,导致我们父子进程无法确定是哪一方先退出,此时 如果父进程陷入逻辑的死循环中,而此时如果子进程退出,就会导致子进程的退出状态无法被回收,那么子进程就会变成僵尸进程。为了防止僵尸进程的产生,所以子进程在进行的时候,就让父进程进行进程等待, 以便子进程可以正常退出。
pid_t wait(int* status);
- status 是wait函数的一个参数,在函数调用时可以获取子进程的退出状态(不是退出码)
- wait函数的返回值: 如果成功,则返回子进程的pid;失败则返回-1.
查看进程状态
ps -ef | grep wait
ps aux | grep wait
调用堆栈查看程序卡顿的地方
pstack 9002 (9002为父进程的pid)
- 进程阻塞: 当调用函数需要等待一定条件满足的时候,如果条件不满足,则一直等待,如果条件满足,则函数逻辑之后正常返回。
- 非阻塞: 当调用函数需要等待一定条件的话,如果该条件不满足,则直接返回,如果条件满足,则调用函数的逻辑之后再返回。
pid_t waitpid(pid_t pid, int *status, int options);
- pid: 要等待哪一个子进程 (pid = -1 ,等待任意一个子进程,和wait等效;pid > 0 ,等待其进程id与pid相等的子进程)
- status: 退出的子进程的退出状态
- options: 设置当前的waitpid是阻塞的还是非阻塞的 (WNOHANG 非阻塞;0阻塞)
- 返回值: 当正常返回的时候waitpid返回收集到的子进程的进程ID;如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在。
waitpid(-1,NULL,0) == wait(NULL)
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
pid_t pid = fork();
if(pid < 0)
{
printf("pid fork error!\n");
}
else if(pid == 0)
{
//child
printf("i am child!\n");
while(1)
{
printf("我给你讲\n");
sleep(1);
}
exit(1);
}
else
{
printf("i am father!");
int status;
//while(waitpid(pid,NULL,WNOHANG) == 0);
//int ret = waitpid(-1,&status,0);
int ret = wait(&status);//status 获取子进程退出状态
//ret 是子进程的pid
printf("status = [%d] [%d]\n",status,ret);
while(1)
{
printf("我不听\n");
sleep(1);
}
}
return 0;
}
获取子进程status
status (查看进程退出码,退出码在内存的低16位)
//利用宏来实现提取退出码
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
进程程序替换
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
执行进程程序替换的函数
int execl(const char *path, const char *arg, ...);
- path: 带路径的可执行程序( ls /uer/bin/ls)
- arg: 给可执行程序传递的参数,第一个参数是可执行程序的名称 ls -a
- …: 可变参数列表,就像scanf,printf一样,以NULL结尾
- 返回值: 只有失败的时候才有返回值,返回值为-1,成功替换后,就不会返回了
int execlp(const char *file, const char *arg, ...);
- file: 可执行程序名称,一定要注意当前程序是否在环境变量PATH中可以被找到,/usr/bin/ls(带路径的可执行程序);函数在执行的时候,会先在环境变量中找当前可执行程序的名称,如果环境变量中没有找到,那么file就只能是一个可执行程序的路径,可以是相对路径也可以是绝对路径,就会根据这个路径找该可执行程序,没有找到就返回-1了,进程程序替换失败。
- arg: 可执行程序传递的参数,第一个参数是可执行程序的名称
- 如果exec函数名称当中带有p,则表示会搜索环境变量,并且第一个参数带p的传入可执行程序的名称,不带p的则传入带路径的可执行程序,路径可以是相对路径也可以是绝对路径
int execle(const char *path, const char *arg, ..., char * const envp[]);
- path: 带路径的可执行程序
- arg: 可执行程序的参数,以NULL结尾
- envp: 程序员自己组织环境变量,如果不传入环境变量则认为当前进程没有环境变量
int execv(const char *path, char *const argv[]);
- path: 带路径的可执行程序
- 定参函数argv: 要给可执行程序传递的参数,第一个参数是该程序的名称,以NULL结尾
int execvp(const char *file, char *const argv[]);
- file: 可执行程序的名称,同时这个程序得要在PATH环境变量中被找到,否则程序替换就会失败;或者也可以传入一个带有路径的可执行程序。
int execve (const char *filename, char *const argv [], char *const envp[]);
- filename: 可执行程序的名称
- argv: 定参的函数,argv要给可执行程序传递的参数,第一个参数是该程序的名称,后面为需要执行的操作,以NULL结尾
- envp: 程序员自己组织的环境变量,如果不传入环境变量则认为当前进程没有环境变量。
命名理解:
- l (list): 表示参数采用可变参数列表
- v (vector): 表示是一个定参的函数,使用数组进行传参
- p (path): 有p则自动搜索环境变量PATH
- e (env): 表示自己维护环境变量
模拟实现xshell
- 启动一个父进程,父进程从标准输入里读取用户输入的内容
- 父进程区分用户输入的内容当中那部分是命令,那部分是命令行参数
ls -a (ls 是命令 -a 是命令行参数) - 父进程fork出子进程后,要让子进程进行进程程序替换,执行用户输入的命令。父进程进行进程等待,等到子进程结束
- 等到子进程退出之后,则进入循环获取用户输入
输入格式: ls -a(中间是空格)
/*1.父进程从标准输入中读取用户输入命令
*2.需要创建一个子进程,让子进程执行命令程序
*3.父进程需要进程等待,等待子进程的退出,然后循环去获取命令
*/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/wait.h>
#include <stdlib.h>
char command[1025] = {'\0'};
//获取用户输入的命令
int get_command()
{
memset(command,'\0',sizeof(command));
printf("[duchenlong@localhost ~]$ ");
fflush(stdout);
if(fgets(command,sizeof(command)-1,stdin) == NULL)
{
printf("fgets error\n");
return 0;
}
//printf("command : %s\n",command);
return 1;
}
//执行命令
void execute(char* argv[])
{
if(!argv[0])
{
printf("execute error!\n");
return ;
}
pid_t pid = fork();
if(pid < 0)
{
printf("fork pid error!\n");
}
else if(pid == 0)
{
//child 进程程序替换
execvp(argv[0],argv);
exit(0);
}
else
{
//father 进程等待
waitpid(pid,NULL,0);
}
}
//分割输入的命令,将命令中的空格都替换成\0
void my_split(char* str)
{
if(!str || (*str == '\0'))
{
printf("command error!\n");
return ;
}
char* argv[1025] = {0};
int len = 0;
while(*str)
{
char* src = str;
while(*str && !isspace(*str))
{
str++;
}
if(*str)
{
*str = '\0';
str++;
}
//printf("src = %s ",src);
argv[len++] = src;
//printf("argv[%d] = %s \n",len-1,argv[len-1]);
}
argv[len] = NULL;
execute(argv);
}
int main()
{
printf("Welcome to minishell!\n");
printf("quit plase input : Q\n");
while(1)
{
if(get_command() == 0)
{
continue;
}
if(command[0] == 'Q')
{
printf("The forthcoming minishell!\n");
break;
}
my_split(command);
}
return 0;
}
守护进程
现在的业务程序一般都有24小时全体在线提供服务,这意味着提供业务程序的进程必须24小时都存在,但是,程序或多或少还是会有一些漏洞,24小时随时在线,就需要有简单处理漏洞的能力,万一程序卡在一个地方,那不就耽搁好的事的嘛。
这个时候就需要守护进程来,守护进程先创建一个业务进程,让业务进程先跑,然后业务进程随时向守护进程更新自己的状态,守护进程就负责接收,然后对比和上一次更新的业务进程状态,要是状态一模一样,就说明业务进程出错了,这时直接干掉业务进程,再重新创建一个业务进程继续跑,继续更新。
如果一个业务进程崩溃,守护进程对比判断,销毁,重建一个新的业务进程也是需要时间的,这样就不能做到24小时服务,试想一下,假如正想跟女盆友表白,忽然这个时候网络卡顿了,女盆友被截胡了,这假如会有多伤心啊,而分布式就可以优化这个问题。
分布式
- 分布式,顾名思义就是在每一个地方都安插一个后台服务的进程,并有多台电脑跑相同的进程,如果同一个地区的一个进程崩溃掉,就可以直接切换另一台同进程的电脑,以便无缝隙连接;而如果一个地区的网络都出现问题,就迅速连接到另一个地区的后台进程,多地区多进程,同时跑,除非全部都瘫痪了,看上去很难出现问题。
- 所解决的问题就是怕服务崩溃导致无法对用户进行服务。