【Linux】进程概念 && 进程控制

进程概念

1.程序和进程的区别

程序是保存在硬盘、光盘等介质中的可执行代码和数据,是硬盘(外设)上的一个普通文件。
进程是在CPU及内存中运行的程序代码(就是被加载到内存里的程序),是一个程序的运行中的描述–pcb,通过这个描述操作系统可以实现对一个程序的运行调度管理

2.描述进程—PCB

Linux下的PCB是:task_struct
task_struct内容分类:

  • 标示符: 描述本进程的唯一标示符,用来区别其他进程。
  • 状态: 不同的状态干不同的事情。任务状态,退出代码,退出信号等。
  • 优先级: 决定获取CPU资源的优先权(相对于其他进程的优先级(让程序运行更加良好))。
  • 程序计数器: 保存程序在CPU上即将执行的下一个指令的地址
  • 内存指针: 指向要调度运行程序代码以及数据在内存中的位置(包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针 )
  • 上下文数据: 保存CPU上正在处理的数据
  • I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  • 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

3.组织进程

所有运行在系统里的进程都以task_struct链表的形式存在内核里。

4.查看进程

可以通过/proc系统文件查看
同样可以使用top和ps这些用工具来获取

  • ps aux以列表的形式显示出进程信息
  • ps aux | grep 用于查看某一个进程的信息
  • top 查看进程动态信息

进程控制

1.进程创建(fork函数)

【 进程状态(进程状态查看、僵尸进程、孤儿进程)、进程的优先级、环境变量、进程地址空间】

fork函数
是通过系统调用创建进程的(创建一个子进程),其
函数原型是pid_t fork(void)
注意
fork()有两个返回值,若调用成功,给父进程(原来的进程)返回子进程的pid,给子进程(新建的进程)返回0;若调用失败,则父进程返回-1

  • 父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝),因为进程运行时具有独立性
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{   
int ret=fork();
if(ret<0)//调用失败,返回-1
    {
        perror("fork");
        return -1;
    }
    else if(ret==0)//下面是同时执行的
    {//child子进程
        printf("I am child: %d!,ret:%d\n",getpid(),ret);//子进程返回0
    }
    else
    {//father父进程
       printf("I am father: %d!,ret:%d\n",getpid(),ret);//父进程返回子进程的pid
    }
    sleep(1);
    return 0;
} 

Linux下运行结果如下:
在这里插入图片描述

问题:

1.子进程返回0的原因是:
一个进程只有一个父进程,所以子进程可以调用getppid,从而获得父进程的ID号

  • 父进程返回子进程的pid, 子进程返回0就像一个父亲有多个孩子,而一个孩子只有一个父亲
  • fork之后,父子进程哪个先运行?
    不一定,fork之后,说明父子进程创建成功了,系统里多了进程,但是系统里是有很多进程的,先运行哪个是由操作系统的调度器决定.
    -父子进程都是从fork函数之后的代码开始执行
    例如

if(fork()) {} else {} printf(“123\n”);
打印结果两个进程都会有123这句

2.fork调用失败的原因:
系统中有太多的进程;实际用户的进程数超过了限制
3.为什么fork之后有两个返回值?
这是因为父进程可能有很多子进程,所以必须通过这个返回的子进程ID来跟子进程,而子进程只有一个父进程,它的ID可以通过getppid取得

fork常规用法

  • 一个父进程希望复制自己,使父子进程同时执行不同的代码段。eg:父进程等待客户端请求,生成子进程来处理请求
  • 一个进程要执行一个不同的程序。eg:子进程从fork返回后,调用exec函数

写时拷贝
意思就是,通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。这样一来,不仅节省了时间,也节省了资源

2.进程终止

进程终止具体分三种:
1.代码运行完毕,结果正确(正常退出)
2.代码运行完,结果不正确(正常退出)
3 代码异常终止(异常退出)

正常终止可以有不同的退出方式:
1.从main函数中返回,main函数中的return值是当前进程退出时的退出码,我们可以通过echo $? 查看上一个进程退出时的退出码

2.调用 exit 或 _exit函数,这两个都是终止进程的,但还有点区别,exit在终止前是要刷新缓冲区的,而 _exit可直接终止进程
3. return退出:执行return n等同于执行exit(n),因为调用main函数运行时,函数会将main的返回值当做exit的参数

通过下面的代码比较下exit()与_exit():
exit函数:

#include<stdio.h>                                                                  
#include<unistd.h>
void exit(int ststus);
int main()
{
  printf("hello!"); //printf函数有输出缓冲区
  sleep(3);//睡眠3秒再打印
  exit(0);
}

printf函数有输出缓冲区,因为exit会在终止前刷新缓冲区,从而打印 hello!
运行结果如下:
在这里插入图片描述

_exit函数:

 #include<stdio.h>                                                                  
 #include<unistd.h>
 void _exit(int ststus);
 int main()
 {
     printf("hello!");
     sleep(3);
     _exit(0);
 }

exit函数直接终止,所以运行结果如下:
在这里插入图片描述
异常终止:在等待过程中,wait或waitpid方法中的参数status收到了信号

3.进程等待

之前我们了解过僵尸进程,知道了它是什么状态。也知道如果不及时回收,就会导致内存泄漏的危害。所以,只要让子进程退出后,父进程接收子进程的退出信息就可以避免,即父进程通过进程等待的方式,回收子进程的资源,获取子进程的信息。(僵尸进程如果不太清楚,可以点上面链接了解哈)
所以,也为了防止内存泄漏,进程等待就非常有必要的!
父进程获取子进程的信息就要通过进程的退出结果来判断了。

进程等待的方法
在了解方法之前,我们需要知道获取子进程的一个status参数

  • wait 和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充
  • 如果传递NULL,表示不关心子进程的退出状态信息

wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int* ststus)

模拟实现下:

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<sys/wait.h>
 4 #include<unistd.h>
 5  int main()
 6 {        
 7     pid_t id = fork();
 8     if(id == 0)
 9     {    
 10         printf("child ...\n");                                                     
 11          sleep(1);
 12    }    
 13     else 
 14     {    
 15       printf("father.....\n");
 16       pid_t ret =wait(NULL);//不关心子进程的退出信息,调用wait,等子进程,等待成功,返回所等待子进程的pid
 17       if(ret>0)
 18       {  
 19           printf("wait success...: %d",ret);
 20       }  
 21     }    
 22 }        

waitpid方法

pid_t waitpid(pid_t pid, int *status, int options);

返回值:

  • 当正常返回时,waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:
pid:

  • pid=-1,等待任一个子进程。与wait等效
  • pid>0 等待其进程ID与pid相等的子进程

status:(通过宏提取信息)

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(是通过检测退出码中的特定比特位来查看进程是否正常退出
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码

options:(等待方式默认是阻塞方式(默认0)

  • WNOHANG:(代表非阻塞方式)若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回进程的ID。

4.进程程序替换

替换原理
用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用exec函数时,该进程的地址空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。

替换函数
这六种替换函数以exec开头,统称exec函数:(执行什么,怎么执行

int execl(const char *path,const char *arg, ...);
int execlp(const char *file,const char *arg, ...);
int execle(const char *path,const char *arg, ... ,char *const envp[]);
int execv(const char *path,char *const argv[]);
int execp(const char *path,char *const argv[]);
int execve(const char *path,char *const argv[],char *const envp[]);

这些函数如果调用成功则加载新的程序,从启动代码开始执行,不再返回。
调用出错,返回-1(exec函数只有出错的返回值,没有成功的返回值
l: 参数列表
v:参数用数组
p:自动搜索环境变量PATH
e:自己维护环境变量

例:

int main()
  {    
       char *const argv[]={"ps","-ef",NULL };
       char *const envp[]={"PATH=/bin:/user/bin","TERM= console",NULL};
       execl("bin/ps","ps","-ef",NULL);
      execlp("ps","ps","-ef",NULL);
      execle("ps","ps","-ef",NULL,envp);
      execv("bin/ps",argv);
      execvp("ps",argv);
      execve("ps",argv,envp);
      return 0;
  }  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值