进程控制(linux)

进程优先级

  • 进程优先权是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

  1. vfork创建出来的子进程PCB也是拷贝父进程的PCB,但是子进程的PCB指向父进程的虚拟地址空间
  2. 会带来父子进程调用栈混乱的问题
  3. 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

  1. 启动一个父进程,父进程从标准输入里读取用户输入的内容
  2. 父进程区分用户输入的内容当中那部分是命令,那部分是命令行参数
    ls -a (ls 是命令 -a 是命令行参数)
  3. 父进程fork出子进程后,要让子进程进行进程程序替换,执行用户输入的命令。父进程进行进程等待,等到子进程结束
  4. 等到子进程退出之后,则进入循环获取用户输入
    输入格式: 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小时服务,试想一下,假如正想跟女盆友表白,忽然这个时候网络卡顿了,女盆友被截胡了,这假如会有多伤心啊,而分布式就可以优化这个问题。

分布式

  • 分布式,顾名思义就是在每一个地方都安插一个后台服务的进程,并有多台电脑跑相同的进程,如果同一个地区的一个进程崩溃掉,就可以直接切换另一台同进程的电脑,以便无缝隙连接;而如果一个地区的网络都出现问题,就迅速连接到另一个地区的后台进程,多地区多进程,同时跑,除非全部都瘫痪了,看上去很难出现问题。
  • 所解决的问题就是怕服务崩溃导致无法对用户进行服务。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值