当前位置:主页 > 查看内容

进程

发布时间:2021-06-15 00:00| 位朋友查看

简介:1.进程的概念 问1 什么是程序什么是进程有什么区别 程序是静态的概念gcc xxx.c -o pro. 磁盘中生成pro文件叫做程序 进程是动态的概念指的是程序的一次运行活动通俗来说就是程序跑起来了系统中就多了一个进程 程序 1存放在磁盘上的指令和数据的有序集合文件 2……

1.进程的概念

问1:什么是程序,什么是进程,有什么区别?
程序是静态的概念,gcc xxx.c -o pro. 磁盘中生成pro文件,叫做程序
进程是动态的概念,指的是程序的一次运行活动,通俗来说就是程序跑起来了,系统中就多了一个进程

程序:
(1)存放在磁盘上的指令和数据的有序集合(文件)
(2)静态的

进程:
(1)执行一个程序所分配资源的总称
(2)进程是程序的一次执行过程
(3)动态的,包括创建、调度、执行和消亡

理解:cpu不能直接访问磁盘,要先把程序中的内容(指令和数据)加载到内存中,这样CPU才能去执行指令,才能获取到数据,因此要执行程序,要分配内存空间,分配CPU资源,这些资源的总称即为进程

一个程序执行的时候会创建多个进程

问2.什么是进程标识符?
每个进程都有一个非负整数表示的唯一ID,叫做pid,类似身份证,可以和之前的文件描述符fd一样理解

Pid = 0; //交换进程(swapper)
作用:进程调度
Pid = 1;//init进程
作用:系统初始化

编程调用getpid函数获取自身的进程标识符,getppid获取父进程的进程标识符

在这里插入图片描述
通过系统数据段,可以使得操作系统有效的管理进程,系统数据段中主要包含:进程控制块CPU寄存器值堆栈

1、进程控制块(pcb)
作用:存放进程的属性
有:
(1)进程标识PID:类似于人的身份证
(2)进程用户:通过用户检查是否具有某种权限
(3)进程状态、优先级:决定进程调度顺序以及进程分配时间片的长短
(4)文件描述符表:表明打开了哪些文件

2、CPU寄存器值
每个进程会保存他用到寄存器的值
比如:pc寄存器(程序计数器)
用于存放进程下一条指令的地址

当进程的时间片用完,他就会让出CPU,等到下次被调度执行的时候,进程不会重新开始执行,而是会从上一次停止的地方开始继续往下执行,因此就需要从pc中取出下一条指令执行的地址。

3、堆栈
操作系统会用到进程栈,所有的局部变量,函数的参数等都会用到

2.进程类型

1.交互进程
在下shell启动时,通过终端与用户交互。用户通过终端输入,进程进行接受并处理,并将结果打印在终端上。是一种可以在前台运行,也可以在后台运行

前台进程可以从终端输入,也可以从终端输出
后台进程无法从终端读取输入,但可以往终端输出

2.批处理进程
和终端无关,被提交到一个作业队列中以便顺序执行

批处理进程一般是管理员经常用到,开发人员很少用到

3.守护进程(重要)
和终端无关,一直在后台运行
很多服务器都是以守护进程的形式在运行
系统关闭,守护进程才关闭,生命周期很长

3.进程状态

(1)运行态,也分为正在运行的运行态和准备运行的准备态

(2)等待态,也成阻塞态,休眠态,指的是进程等待一个事件的发生或某种系统资源

当有资源的时候,操作系统会唤醒进程,进程又会回到运行态

也分为不可中断和可中断,通过会不会被信号打断来进行区分

(3)停止态,进程被中止,收到信号后可继续执行

最常用的是GDB去调试一个程序的时候,设置断点,运行到断点时候会停下来,停下来的原因就是GDB向进程发了一个中止信号。

(4)死亡态,也叫僵尸态,已经终止的进程,但是pcb(放了进程返回的值以及进程结束的一些信息)没有被释放,其他资源释放了

进程状态图如下:
在这里插入图片描述

4.查看进程信息

1.ps
作用:查看系统进程快照
(1)ps -ef:查看所有进程的简要信息
(2)ps aux:还能显示进程当前状态
进程状态:
R:运行态
S:等待态
Z:僵尸态
+:表示为前台进程

两种用法都可以配合管道进行过滤 |grep

2.top
作用:查看进程动态信息,可以查看那些进行最占用资源,每个3s进行一次统计

3./proc
作用:查看进程详细信息,proc即目录的意思
在该目录下查看进程ID号,再进入进程对应目录查看

5.进程相关命令-----修改优先级

1.nice
作用:按**用户指定**的优先级运行进程

当使用yop命令时候可以看到有NI这一列,范围为-20~19,默认为0,NI的数值越小代表优先级越高。

例:nice -n 2 ./test
其中:2就是指定优先级,普通用户只能指定正整数,管理员用户可以任意指定。

2.renice
作用:改变正在运行的进程的优先级

例:renice -n 2 xxx
其中,xxx为要修改进程的进程号,普通用户只能降低优先级,管理员用户可以降低,也可以提高。

3.jobs
作用:查看后台进程(后台作业)

例:./test &为运行一个后台进程
得到[1] 29140
[1]为作业号,29140为进程号
再使用jobs命令就可以进行查看

4.bg
作用:将挂起的进程在后台运行

5.fg
作用:将后台运行的进程放到前台运行

例:
fg 1:将一个后台的进程放到前台

ctrl+z:让当前前台进程在后台挂起

bg 2:让后台作业在后台运行起来

5.创建进程

使用fork函数创建一个进程

pid_t fork(void);

fork函数调用成功,返回两次
成功时父进程返回子进程的ID号,子进程返回0

返回值为0:代表当前进程为子进程
返回值为非负数:代表当前进程为父进程
返回值为-1:调用失败

fork创建一个子进程的一般目的
(1)一个父进程希望复制自己,使父,子进程执行不同的代码段。这在网络服务进程中是常见的------父进程等待客户端的服务请求。在这种请求到达时,父进程调用fork,使子进程处理此请求,父进程继续等待下一个请求到达。

(2)一个进程要执行一个不同的程序,这对shell是常见的情况,在这种情况下,子进程从fork返回后立即调用exec

例子:
getpid():获取当前进程,与pid进行比较

#include <stdio.h>
#include <unistd.h>
int main()
{
        pid_t pid;
        printf("pid = %d\n",getpid());
        pid = fork();

        if(pid>0){
                printf("this is father pid,father pid is %d\n",getpid());
        }else if(pid == 0){
                printf("this is child pid,child pid is %d\n",getpid());
        }else{
                printf("fork error\n");
        }
        return 0;
}

运行结果:
在这里插入图片描述
父子进程:
(1)子进程继承了父进程的内容
(2)父子进程有独立的地址空间,互不影响
(3)若父进程先结束,则子进程会变成孤儿进程,被init进程收养,子进程变成后台进程
(4)若子进程先结束,父进程没有及时回收的话,子进程会变成僵尸进程

思考:
(1)子进程从何处开始运行?
答:从fork()后的下一条语句开始开始执行,注意:子进程并没有执行fork(),因为如果子进程也执行了fork,最终会进去一个死循环,不断创建新的进程。

(2)父进程创建子进程后,父子进程谁先执行?
答:不确定。对于Linux来说,并没有进行相应的规定,最终是看内核的调度,内核先调度谁,谁就先运行。

对上面两个问题的总结即是第三个问题
(3)fork创建的时候发生了什么?

数据段,堆,栈拷贝
代码段共享
父子进程谁先跑,由进程调度决定

验证代码:

#include <stdio.h>
#include <unistd.h>
int main()
{
        pid_t pid;
        int a = 10;
        printf("pid = %d\n",getpid());
        pid = fork();

        if(pid>0){
                printf("this is father pid,father pid is %d\n",getpid());
        }else if(pid == 0){
                printf("this is child pid,child pid is %d\n",getpid());
                a = a + 10;
        }else{
                printf("fork error\n");
        }

        printf("a = %d\n",a);
        return 0;
}

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

vfork函数也可以创建进程,与fork的区别:
1.vfork直接使用父进程存储空间,不拷贝
2.vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行

实例代码:

#include<stdio.h>
#include <unistd.h>
#include<stdlib.h>
int main()
{ 
        pid_t pid;
        int cnt = 0;
        pid = vfork();
        if(pid > 0){
                while(1){
                        printf("cnt = %d\n",cnt);
                        printf("this is father pid,father pid = %d\n",getpid());
                        sleep(1);
                }
        }
        else if(pid == 0){
                while(1){
                        printf("this is child pid,child pid = %d\n",getpid());
                        sleep(1);
                        cnt++;
                        if(cnt == 3){
                                exit(0);
                        }
                }
        }
        return 0;
} 

运行结果:
子进程线运行三次结束后,才运行父进程
在这里插入图片描述

6.进程退出与回收

正常退出:
1.main函数调用return退出
2.进程调用exit(),属于标准c库
3.进程调用_exit()或者 _Exit(),属于系统调用

补充:
1.进程包含多个线程,进程最后一个线程返回
2.最后一个线程调用pthread_exit

异常退出:
1.调用abort
2.当进程收到某些信号时,如Ctrl+C
3.最后一个线程对取消请求做出响应

不管进程如何终止,最后都会执行内核中的同一段代码。这段代码为相应进程关闭所有打开描述符,释放它所使用的存储器等。

1.当子进程退出状态不被收集,会变成僵死进程(僵尸进程),代码与前面一样

 1 #include<stdio.h>
  2 #include <unistd.h>
  3 #include<stdlib.h>
  4 int main()
  5 {
  6     pid_t pid;
  7     int cnt = 0;
  8     pid = vfork();
  9     if(pid > 0){
 10         while(1){
 11             printf("cnt = %d\n",cnt);
 12             printf("this is father pid,father pid = %d\n",getpid());
 13             sleep(1);
 14         }
 15     }
 16     else if(pid == 0){
 17         while(1){
 18             printf("this is child pid,child pid = %d\n",getpid());
 19             sleep(1);
 20             cnt++;
 21             if(cnt == 3){
 22                 exit(0);
 23             }
 24         }
 25     }
 26     return 0;
 27 }

在这里插入图片描述
在这里插入图片描述
s+:正在运行的状态
z+:僵尸进程

2.父进程等待子进程退出,并收集子进程的退出状态
正常退出时,会有退出码,即exit中的参数为几,根据子进程的退出码来判断子进程的完成情况
在这里插入图片描述

pid_t wait(int *status);
status:整形数指针
非空:子进程退出状态放在它所指的地址中
空:不关心退出状态

实例代码:

  1 #include<stdio.h>
  2 #include <unistd.h>
  3 #include <sys/wait.h>
  4 #include<stdlib.h>
  5 int main()
  6 {
  7     pid_t pid;
  8     int status = 10;
  9     int cnt = 0;
 10     pid = vfork();
 11     if(pid > 0){
 12         wait(&status);
 13         printf("child quit,child status = %d\n",WEXITSTATUS(status));
 14         while(1){
 15             printf("cnt = %d\n",cnt);
 16             printf("this is father pid,father pid = %d\n",getpid());
 17             sleep(1);
 18         }
 19     }
 20     else if(pid == 0){
 21         while(1){
 22             printf("this is child pid,child pid = %d\n",getpid());
 23             sleep(1);
 24             cnt++;
 25             if(cnt == 3){
 26                 exit(3);//退出码为3
 27             }
 28         }
 29     }
 30     return 0;
 31 }

在这里插入图片描述
(1)如果其所有子进程都还在运行,则阻塞
(2)如果一个子进程已经终止,正等待父进程获取其终止状态,则取得该子进程的终止状态立即返回
(3)如果它没有任何子进程,则立即出错返回

引入waitpid,区别:
wait使地调用者阻塞,waitpid有一个选项可以使调用者不阻塞

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

pid == -1:等待任一子进程,与wait等效
pid > 0:等待其进程ID与pid相等的子进程 //用的较多
pid == 0:等待其组ID等于调用进程ID的任一子进程
pid < -1:等待其组ID等于pid绝对值的任一子进程
在这里插入图片描述

3.孤儿进程
父进程如果不等待子进程退出,在子进程之前就结束了自己的“生命”,此时子进程就叫做孤儿进程
linux避免系统村存在过多的孤儿进程,init进程(进程ID为1,是系统的初始化进程)收留孤儿进程,变成孤儿进程的父进程

7.exec函数族

(1)进程调用exec函数族执行某个程序
(2)进程当前内容被指定的程序替换
(3)实现父子进程执行不同的程序
实现:父进程创建子进程,子进程调用exec函数族,从而去执行指定的程序,而父进程不会受到影响

这在shell中是很常见的,shell相当于一个父进程,shell创建的子进程会去调用执行用户指定的程序。
博文推荐:exec族函数用法

exec实例代码:

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 
  5 //函数原型:int execl(const char *path, const char *arg, ...);
  6 
  7 int main(void)
  8 {
  9     printf("before execl\n");
 10 
 11     char *argv[] = {"ps",NULL,NULL};
 12   //  if(execvp("ps",argv) == -1) 
 13     if(execv("/bin/ps",argv) == -1)
 14     {
 15         printf("execl failed!\n");
 16 
 17 
 18         perror("why?");
 19 
 20     }
 21     printf("after execl\n");
 22     return 0;
 23 }

8.system函数

int system(const char* command);

system()函数的返回值:
成功:返回进程的状态值
当sh不能执行时:返回127
失败:返回-1

实例代码:

  #include <stdio.h>
   #include <stdlib.h>
  #include <unistd.h>

   int main(void)
   {
     
      if(execv("ps") == -1)   //直接ps
      {
          printf("execl failed!\n");
          perror("why?");
  
      }
      printf("after execl\n");
     return 0;
  }

与exec不同的是最后还是会返回到源程序当中,执行后面的代码

推荐博文:Linux system函数详解

9.popen函数

相比于system的好处:popen可以获取运行的输出结果

FILE *popen(const char *command,const char *type);

参数说明:
command: 是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。

type: 只能是读或者写中的一种,得到的返回值(标准 I/O 流)也具有和 type 相应的只读或只写类型。如果 type 是 “r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。

返回值:
  如果调用成功,则返回一个读或者打开文件的指针,如果失败,返回NULL,具体错误要根据errno判断

int pclose (FILE* stream)

参数说明:
stream:popen返回的文件指针

返回值:
如果调用失败,返回 -1

popen执行某个程序,例如popen(“ps”,“r”);会把这个结果重定位到管道中,将数据往管道里面扔,管道的另外一边调用fread来把数据读到缓冲区

实例代码:

#include <stdio.h>
#include <string.h>
int main(void)
{
    FILE *fp;
    char buf[10240] = {0};
    fp = popen("ps","r");
    fread(buf, 10240, 1, fp);
    printf("%s\n",buf);
    pclose(fp);
    return 0;
}

博文推荐:linux下popen使用心得

10.守护进程

1.守护进程特点
1.1相关介绍:
(1)守护进程是Linux下三种进程类型之一
Linux操作系统包括三种不同类型的进程,每种进程都有自己的特点和属性。

  1. 交互进程是由一个Shell启动的进程。交互进程既可以在前台运行,也可以在后台运行。
  2. 批处理进程和终端没有联系,是一个进程序列。
  3. 监控进程(也称系统守护进程)是Linux系统启动时运行的进程,并常驻后台。例如,httpd是著名的Apache服务器的监控进程。

(2)通常在系统启动时运行,系统关闭时结束
(3)守护进程在Linux中大量使用,很多服务程序都是以守护进程形式运行

1.2特点
(1)始终在后台运行
(2)独立于任何终端(与交互进程的最大区别)
(3)周期性的执行某种任务或等待处理特定事件

2.会话,控制终端
(1)Linux以会话、进程组的方式管理进程
(2)每个进程属于一个进程组
(3)会话是一个或多个进程的集合
会话中运行的第一个进程是shell,因此shell也叫作会话的首进行
(4)一个会话最多为只能打开一个控制终端
(5)终端关闭时,所有相关的进行都会结束

3.守护进程创建(5步)
(1)创建子进程,父进程退出

if(fork() > 0){
	exit(0);
}
子进程变成孤儿进程
子进程在后台运行

(2)子进程创建新会话

if(setsid() < 0){
	exit(-1);
}
子进程变成新的会话组长
子进程脱离原来的终端

(3)更改当前工作目录

chdir('/');
chdir("/tmp");
参数为:指定修改的目录
守护进程一直在后台运行,其工作目录不能被卸载
重设当前目录为cwd

(4)重设文件权限掩码

if(umask(0) < 0){
	exit(-1);
}
文件权限掩码设置为0
只影响当前进程

(5)关闭打开的文件描述符

int i;
for(i=0;i<getdtablesize();i++){
	close(i);
}
getdtablesize返回当前进程打开文件的最大个数
关闭所有从父进程继承的打开文件
已经脱离终端,stdin/stdout/stderr无法再使用
;原文链接:https://blog.csdn.net/qq_51118175/article/details/115584808
本站部分内容转载于网络,版权归原作者所有,转载之目的在于传播更多优秀技术内容,如有侵权请联系QQ/微信:153890879删除,谢谢!

推荐图文


随机推荐