精华内容
下载资源
问答
  • Linux进程概念

    千次阅读 多人点赞 2019-04-09 14:38:35
    进程概念 冯诺依曼体系结构 在进程之前首先要提一下我们的“祖师爷”——冯诺依曼体系结构。 这个是一个计算机入门第一节课必然会提到的知识。 冯诺依曼体系结构提出了计算机采用二进制;计算机应该按照程序顺序...

    进程是什么?在操作系统中,我们经常能听到这样的话。我们要终止一个进程或者杀死一个进程,父进程创建了子进程这一类的话。往往我们听到都会觉得很高大上,这跟编程语言完全不同的感觉,操作了整个计算机。

    进程的概念

    冯诺依曼体系结构

    在进程之前首先要提一下我们的“祖师爷”——冯诺依曼体系结构。

    这个是一个计算机入门第一节课必然会提到的知识。

    冯诺依曼体系结构提出了计算机采用二进制;计算机应该按照程序顺序执行。

    它由输入设备,输出设备,存储器,控制器,运算器组成

    注意!注意!注意!

    1、这里的存储器指的是内存。

    2、不考虑缓存情况,这里的cpu(控制器+运算器)只能对内存进行读写,不能访问外设(输入或输出设备)

    3、外设要输入或输出数据,也只能写入内存或者从内存中读取

    操作系统(0S)

    任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)

    操作系统的存在就是让计算机更加的好用,能更方便的、统筹合理的管理计算机的软硬件资源。

    那么是怎么管理的?

    我们可以先举一个例子,学校的管理,首先我们被辅导员统一管理,辅导员又由院内领导管理,院内领导又由校长管理。

    学生->辅导员->院内领导->校长 这么一个层次结构。但在这之前管理需要制定一个制度来管理,每一个层次的人都遵守这个制度。这样才能按部就班的进行工作的分配。

    那么操作系统也一样。总的来说就是:先描述,再组织

    描述用struct结构体,比如进程有task_struct这样一个结构体来描述

    组织可以用链表或者其他高效的数据结构

    系统调用和库函数概念

    在开发角度,操作系统对外会表现一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用。

    系统调用在使用上,功能比较基础,对用户的要求也相对比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成了库,有了库,就很有利于更上层用户或者开发者进行二次开发

    进程概念

    从用户角度:进程就是一个正在运行中的程序。

    操作系统角度:操作系统运行一个程序,需要描述这个程序的运行过程,这个描述通过一个结构体task_struct{}来描述,统称为PCB,因此对操作系统来说进程就是PCB(process control block)程序控制块

    进程的描述信息有:标识符PID,进程状态,优先级,程序计数器,上下文数据,内存指针,IO状态信息,记账信息。都需要操作系统进行调度。

    那么在Linux操作系统下,怎么查看进程呢

    查看进程

    输入ls /porc指令即可

    进程1

    前面蓝色数字代表的进程的ID。如果你想查看PID为1的进程信息,你需要查看/porc/1这个文件夹

    我们也可以使用ps -ef -aux指令来直接显示进程状态

    进程2

    还有getpid()和getppid()这两个函数用来查看当前程序的进程和父进程PID。

    int main(){
        printf("getpid:%d",getpid());
        printf("getppid:%d",getppid());
        return 0;
    }
    

    进程创建

    Linux中非常重要的函数——fork(),它从已存在的进程中创建一个新进程。新进程为子进程,而原进程为父进程。

    #include <unistd.h>
    pid_t fork(void);
    返回值:
    	父进程:返回值大于0,子进程的pid
    	子进程:返回值等于0
    

    写这么个程序来初始fork函数

    #include <stdio.h>
    #include <unistd.h>   
    int main(){    
      printf("parent pid:%d\n",getpid());    
      int a = 100;    
      pid_t pid = fork();    
      if(pid < 0){    
        return -1;    
      }else if(pid == 0){    
        a = 20;    
        printf("child !! pid:%d----a:%d--%p\n",getpid(),a ,&a);    
        
      }else{    
        sleep(1);    
        printf("parent !! pid:%d----a:%d--%p\n",getpid(), a, &a);    
      }    
      printf("nihaoa %d\n",a);    
      return 0;    
    }  
    

    关于fork函数需要理解,每当调用一次fork函数时,会返回两个两次。一次是在调用进程中(父进程)返回一次,返回值是新派生的进程的进程ID。一次是在子进程中返回,返回值是0,代表当前进程为子进程。如果返回-1,那么则代表在创建子进程的过程中出现了错误。

    将上面代码执行之后

    进程3

    先返回了子进程的pid,之后再返回了父进程的pid。

    fork()相当于创建了一个新的子进程,但是拷贝的是fork()函数之后的所有数据,之前的并不会拷贝。在代码之上就可以看到parentpid只打印了一次

    总的来说:复制pcb,代码共享,但是子进程并非从头开始,而是从fork()函数之后开始,数据独有

    借用一下网上大佬对fork()的理解

    (1)一个进程进行自身的复制,这样每个副本可以独立的完成具体的操作,在多核处理器中可以并行处理数据。这也是网络服务器的其中一个典型用途,多进程处理多连接请求。 
    (2)一个进程想执行另一个程序。比如一个软件包含了两个程序,主程序想调起另一个程序的话,它就可以先调用fork来创建一个自身的拷贝,然后通过exec函数来替换成将要运行的新程序。
    

    那么创建子进程的意义是什么————压力分摊/干其他工作

    进程状态

    进程状态一般有:就绪态,阻塞态,运行态

    在Linux下:R运行状态,S睡眠状态,D磁盘休眠状态,T停止状态,X死亡状态

    这些当我们使用指令ps -aux就可以看到

    僵尸进程

    在进程状态中有两个比较特殊的存在。僵尸和孤儿

    僵尸进程是进程退出后,但是资源没有释放,处于僵死状态的进程。

    产生原因:子进程先于父进程退出,操作系统检测到进程的退出,通知父进程,但是父进程这时候正在执行其他操作,没有关注这个通知,这时候操作系统为了保护子进程,不会释放子进程资源,因为子进程的PCB中包含有退出原因。这时候因为既没有运行也没有退出,因此处于僵死状态,成为僵尸进程。

    #include <stdio.h>    
    #include <stdlib.h>    
    #include <unistd.h>    
    #include <errno.h>                                                                                                         
    int main()    
    {    
      pid_t  pid;    
      //循环创建子进程    
      while(1)    
      {    
        pid = fork();    
        if (pid < 0)    
        {    
          perror("fork error:");    
          exit(1);    
        }    
        else if (pid == 0)    
        {    
      printf("I am a child process.\nI am exiting.\n");    
      //子进程退出,成为僵尸进程    
      exit(0);    
        }    
        else    
        {    
          //父进程休眠20s继续创建子进程    
          sleep(20);    
          continue;
        }       
      }      
      return 0;                            
    }
    

    执行这上面这个程序,子进程中途退出了。

    进程4

    进程5

    z+这个标志就是僵尸进程的标志。

    那么怎么避免僵尸进程的产生?

    我们一般处理就是关闭父进程,这样僵尸子进程也随之消失了。

    所以我们最好设置进程等待,等待子进程完成了工作,并且通知了父进程之后,在退出。

    孤儿进程

    孤儿进程与僵尸进程在理解上可以认为相反。

    父进程先于子进程退出,父进程退出后,子进程成为后台进程,并且父进程为1号进程。

    守护进程:特殊(脱离了与终端的关联+会话的关联)的孤儿进程

    进程优先级

    优先级:决定资源的优先分配权的等级划分

    那么进程为什么具有优先级呢?

    为了让操作系统的运行更加的合理——交互式进程(一旦有操作优先处理)和批处理进程(一直处理程序,但对CPU要求不高)

    设置优先级,可以使用指令ps -elf先查看进程

    可以看到 PRI 和 NI这两个数值

    PRI:优先级 NI:nice值

    PRI是无法直接调整的,但是可以通过调整nice值来调整优先级的大小

    PRI = PRI + NI,但是NI也是有范围的——(-20~19)

    指令操作为renice -n size -p pid

    运行时操作为nice -n size ./main(可执行文件)

    这里稍微提一下,程序在运行时具有并行和并发两种执行。

    并行:CPU资源足够,多个程序同时运行

    并发:CPU资源不够,多个程序切换调度运行(可以看看我之前的一篇关于操作系统的博客,有关调度方法的介绍)

    进程6

    Linux下指令top指令可以查看进程的优先级

    进入top后按“r”–>输入进程PID–>输入nice值

    环境变量

    环境变量是保存系统运行环境参数的变量

    环境变量在安装java的过程中,可能接触过,需要进入系统环境变量,然后设置PATH添加java的路径

    在Linux下可以通过命令指令自己设置

    echo 通过变量名称,查看指定环境变量
    env 查看所有环境变量
    set 查看环境变量以及临时变量
    export 声明一个环境
    unset 删除一个临时变量
    

    常见的环境变量:HOME SHELL USER PATH

    进程7

    环境变量的全局特性:在子进程中获取继承于父进程的环境变量信息

    三种获取环境变量的参数

    main(int argc, char *argc[],char *env[])	参数获取
    
    extern char **environ;	全局变量获取
    
    char *getenv(const char *env_name)	接口获取
    

    写一个获取变量的demo

      #include <stdio.h>    
      #include <stdlib.h>    
          
    W>int main(int argc, char *argv[], char *env[]){    
        //main函数的参数值是从操作系统命令行上获得的。当我们要运行一个可执行文件时,在DOS提示符下键入文件名,再输入实际参数>  即可把这些实参传送到main的形参中去。        
        //C:\>可执行文件名 参数 参数……    
        //argv参数是字符串指针数组,其各元素值为命令行中各字符串(参数均按字符串处理)的首地址    
        //env:字符指针的数组,每一个元素是指向一个环境变量的字符指针                                                       
        int i;                                      
        for(i = 0;i < argc; i++){                   
          printf("argv[%d]=[%s]\n",i , argv[i]);     
        }                                            
        extern char **environ;                       
        for(i = 0;environ[i] != NULL;i++){           
          printf("env[%d]=[%s]\n",i, environ[i]);    
        }                               
        char *ptr = getenv("MYENV");    
        printf("MYENV:[%s]\n",ptr);    
        return 0;                 
      } 
    

    进程8

    如果输入export MYENV="10000",在运行程序

    MYENV将变成MYENV:[10000]

    程序地址空间

    内核空间
    运行参数/环境变量
    栈区(从上往下取)中间有共享区
    堆区(从下往上取)中间有共享区
    初始化全局数据
    未初始化全局数据
    代码段

    地址是什么?地址是内存的编号,指向内存的一块区域

    虚拟地址空间:

    mm_struct{
    long int size
    code_start
    code_end
    data_start
    a
    data_end
    }
    

    进程9

    上图之中,中间的物理内存地址,两边的为虚拟内存地址。

    右边是父进程将虚拟地址通过页表查找到物理内存的地址,这时父进程在运行一个程序。此时父进程又创建了一个子进程(图最右)。子进程也通过页表找到物理内存的地址,这时子进程运行的程序在物理内存的另一个新空间运行。

    这是虚拟内存地址使用的过程。它的作用就是保持进程的独立性,通过页表映射物理地址,充分的利用物理地址,增加内存访问控制。

    这就是进程有关的知识。总结一下

    进程从用户角度和操作系统角度去理解。前者就是一个运行的程序,后者表示运行一个程序,需要描述一个程序的运行过程,通过一个结构体task_struct{}来描述,叫做PCB。对操作系统来说,进程就是PCB

    进程的创建需要一个我们必须要掌握的函数——fork()函数,创建一个子进程。并且fork()函数会有两个返回值,在返回的过程中父进程之前的数据不进行拷贝,之后与父进程的运行一致。创建子进程是为了分摊压力/干其他工作

    进程的状态有运行态,睡眠状态,磁盘休眠状态,停止状态,死亡状态。每一种状态是容易理解的。但是还有两个特殊的进程

    僵尸进程和孤儿进程,前者是进程退出后资源没有释放,操作系统通知父进程,但是父进程此时正在处理其他事情,没有关注子进程的退出通知,系统为了保护资源,没有释放掉,并且在子进程的PCB中也保留了退出原因,此时既没有运行也没有完全退出,处于了僵死状态。但是后者是父进程先于子进程先退出,子进程成为了后台程序,由一号进程接管。

    展开全文
  • 进程概念进程控制

    千次阅读 2018-08-18 18:37:12
    进程概念  1. 概念:  a. 进程是程序的一次动态执行过程  b. 担当分配系统资源(CPU时间、内存)的实体。(从内存角度)  2. 描述进程---PCB  进程的信息被放在一个叫做进程控制块的数...

        这里将介绍进程的基本概念,什么是进程,如何描述和组织进程,接着讨论进程的状态,最后介绍进程 控制

    进程概念

        1.  概念:

                a. 进程是程序的一次动态执行过程

                b. 担当分配系统资源(CPU时间、内存)的实体。(从内存角度)

        2. 描述进程---PCB

                进程的信息被放在一个叫做进程控制块的数据结构中,在Linux中描述进程的结构体叫做task_struct。task_struct是Linux内核的一种数据结构,它会被装载到内存里面包含着进程的信息。

    task_struct内容分类:

    • 标识符:描述本进程的唯一标示符,用来区分其他进程
    • 状态:任务状态,退出代码,退出信号
    • 优先级:相对于其他进程
    • 程序计数器:程序中即将被执行的下一条指令的地址
    • 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
    • 上下文数据:进程执行时处理器的寄存器中的数据
    • I/O状态信息:包括显示的I/O请求,分配给进程I/O设备和被进程使用的文件列表
    • 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
    • 其他信息

        3. 组织进程

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

        注:创建一个进程需要做哪些事情?

               1. 要创建一个进程,必须把这个程序的代码和数据加载到内存,操作系统必须为该进程创建对应的task_struct.

               2. 再用双链表将其组织起来 。

    进程的状态

        

         除了以上提到的几个状态外,这里重点介绍下:Z-僵尸进程和孤儿进程

    僵尸进程

          僵尸进程是一个比较特殊的状态,产生僵尸进程的原因是子进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程。

    •      模拟僵尸进程的场景

       eg:创建维持30s的僵尸进程的例子。

    一个终端运行,一个终端进程监视

    看到结果:

    •     僵尸进程的危害

               1.  父进程如果一直不读取,则进程一直处于僵尸状态维护退出状态本身就需要数据维护,所以会存在资源的浪费。

               2.   内存泄露。

    孤儿进程

         父进程退出,子进程就称之为“孤儿进程” 。 孤儿进程被1号init进程领养,当然要有init进程回收。

    •   模拟僵尸进程的场景

    程序:

    运行程序&结果查看

    进程控制

    进程创建

    fork函数

          fork函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

    创建进程的一般工作

    1. 分配一个PCB,拷贝父进程的PCB的绝大部分数据
    2. 给子进程分配资源
    3. 复制父进程地址空间的数据
    4. 将进程状态置为就绪态,插入就绪队列

    fork函数的返回值

         子进程返回0,父进程返回的是子进程的pid。

     eg: 当一个进程调度fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都可以开始它们自己的旅程 ,如下进程:

    为什么会输出这样的消息呢?

    因为fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注:fork之后,谁先执行完全由调度器决定。

    通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。但一般情况下,一个父进程希望复制自己,是父子进程同时执行不同的 代码段。(子进程从fork函数返回后,调用exec函数)

    fork调用失败的原因

    • 系统中有太多的进程(内存不够,资源不够)
    • 实际用户的进程数超过了限制

    vfork函数

    • vfork函数也是用来创建子进程的。
    • vfork用于创建一个子进程,则子进程和父进程共享地址空间,fork的子进程具有独立地址空间
    • vfork保证子进程先运行,在它调用exec或(exit)之后父进程才可能被调度运行

    进程终止

    进程的退出场景

    • 代码运行完毕,结果正确
    • 代码运行完毕,,结果不正确
    • 代码异常终止

    进程常见的退出方法

    正常退出

    • 用main退出
    • 调用exit
    • _exit

    异常退出

    • ctrl+c  ,信号终止

    _exit与exit的区别

        进程终止时,_exit是强制终止的。

        exit会执行用户通过atexit或on_exit定义的清理函数,关闭所用打开的流,所有的缓存数据均被写入,调用_exit.

    进程等待

        之前说的僵尸进程,就是子进程退出时,父进程不管不顾。进程一旦进入僵尸状态,那就刀枪不入,连kill -9也无能为力。所以父进程需要通过进程等待的方式,回收子进程资源,获取子进程退出信息。

    进程等待的必要性

        回收僵尸进程,避免内存泄漏,保证子进程优先退出,父进程最后退出,父进程要获得子进程的退出信息,从而保证后序的机制

    进程等待的方式

    wait和waitpid的区别:

    wait会令调用者阻塞直到某一个子进程退出

    waitpid则可以通过设置一个选项(options)来设置为非阻塞,另外waitpid并不是等待第一个结束的进程,而是等待参数中pid指定的进程

     

       

          

           

               

     

    展开全文
  • 进程的基本概念

    万次阅读 2018-11-08 23:14:34
    1.什么是进程?  1. 进程就是运行起来的程序,程序运行起来需要被加载到内存中。(这是站在用户的角度看待进程的)  2. 进程就是操作系统的描述,这个描述叫PCB(进程控制块),Linux下PCB有自己的名字叫task_...

    1.什么是进程?

          1. 进程就是运行起来的程序,程序运行起来需要被加载到内存中。(这是站在用户的角度看待进程的)
          2. 进程就是操作系统的描述,这个描述叫PCB(进程控制块),Linux下PCB有自己的名字叫task_struct.而操作系统就是使用task_struct结构体描述进程,使用双向链表来将这些结构体组织起来进行管理。

    task_struct结构体的内容分类(进程的描述信息)下面简单的介绍几类:

        进行(运行)中的程序------程序是死的。放在硬盘中,当运行起来>的时候就会被加载到内存中。        站在操作系统的角度:进程是PCB(进程控制块) ---task struct
                    标识符:描述本进程的唯一标识符,用来区别其他进程的。
                    状态:任务状态,退出代码,推出信号等。
                    上下文数据:进程执行时处理器的寄存器中的数据。
                    程序计数器:记录每个进程下一次要进行的指令的地址。
                    优先级:相对于其他进程的优先级。

                    文件的状态信息
                    记账信息。

    如果想深入的了解的话可以查看大佬的详解:https://blog.csdn.net/bit_clearoff/article/details/54292300

    2.查看进程信息的命令

            ps -ef:查看所有的进程信息。
            ps aux:查看进程的详细信息。
            top;(查看进程的信息)
            查看你所要的进程信息: ps -ef | 名字。
                    getpid();(在代码中获取一个进程的id)(一个系统的调用接口)

    #include<stdio.h>
    
    int main()
    {
        printf("%d\n", getpid());//使用getpid()来获取一个进程的id
        return 0;
    }

    3.如何创建一个进程:

            shell---命令行解释器(shell相当于我们命令行的界面,如果我们从键盘中敲入一个指令时,shell就会执行这条指令)。

    系统调用open返回的是一个文件描述符,类型为int,C库中的fopen返回的是文件流指针:FILE*。
    我们所说的缓冲区(printf),用户态的一个缓冲区,是文件流指针自带的
        printf,  fwrite这些库函数都是先把数据写入到缓冲区中,等缓冲区写满了之后或者其他;条件满足之后才会写入到真正的文件中,而系统调用没有这个用户态的缓冲区(是直接将数据写入到文件中)这个说法不准确
    fork() ---(通过复制调用生成一个新的进程)创建一个子进程。
                    对于父进程来说,fork()的返回值是子进程的pid
                    对于子进程来说,fork()的返回值是0

            因为子进程是根据父进程为模板来创建的,因此父子的代码段是一样的(父子进程运行的是同一段代码)。但是父子进程的返回值不同。父子进程的数据不相同。子进程的数据会另外开辟内存来存放。
            对于代码来说父子进程是相同的(相同的代码只是fork()之下的代码,而不是从头到尾),但是父子进程的数据是独有的(写时复制技术)。
            父进程是从代码头到代码的结束。子进程是从fork()开始到代码结束。
            用户就是通过返回值来判断分辨父子进程,来进行代码的分流。

            kill 进程号----》杀死进程。

    4.进程的状态

            R(running)            运行态:并不意味着程序一定在运行中,它表明进程要么在运行中要么在运行队列里。
            S(sleeping)           可中断的休眠(浅度睡眠)
            D(disk sleep)         不可被中断的休眠,只能通过指定的方式--->唤醒(深度睡眠)
            T(stopped)            停止的状态
            t(tracing stop)       追踪状态
            X(dead)                 死亡状态
            Z(zombie)             僵死态

    5.僵尸进程

            产生原因:
                    子进程先于父进程退出它会保存自己的退出状态,因此它不会自动释放所有资源。子进程退出后会通知父进程子进程退出了,然后让父进程去获取退出状态,然后完全释放子进程资源。假如父进程不管子进程的退出状态,那么这个子进程就会变成一个僵死进程。
            僵尸进程的危害:资源泄露,正常的进程可能无法创建。
            僵死进程如何杀死:杀死它的父进程就可以了。

    下面是一个僵尸进程的演示:

    #include<stdio.h>
    #include<stdlib.h>
    
    int main()
    {
            pid_t pid = fork();
            if(pid < 0)
            {
                    perror("fork error");
                    return -1;
            }
            else if(pid == 0)
            {
                    printf("this is a child\n");
                    sleep(10);//为了方便我们观察子进程退出之前的状态
                    exit(0);
            }
            else
            {
                    sleep(30);//为了使子进程先于父进程退出
                    printf("this is parent\n");
            }
            while(1)
            {
                    sleep(1);
            }
            return 0;
    }
    

    当子进程还没有退出的时候:此时的子进程和父进程都没有退出。(使用ps aux查看进程的状态)

     当子进程退出的时候:因为父进程还没有退出的时候,而子进程退出了,所以子进程就变成了一个僵尸进程。如下所示:(第一个是父进程,第二个是子进程(现在变成了僵尸进程))。

     上面这个就是一个僵尸进程。

    6.孤儿进程

            产生的原因:
                    父进程先于子进程退出,那么这个子进程就会变成一个孤儿进程,并且进入后台运行。
            特性:
                    它原来的父进程退出后。它的父进程就变成了init进程,如果子进程退出后,init进程将负责释放子进程的资源,所以子进程就不会变成一个僵死进程。
            孤儿进程运行时,是后台运行,你可以继续输入其他的指令。

    下面是孤儿进程的代码演示:

    #include<stdio.h>
    #include<unistd.h>
    
    int main()
    {
            pid_t pid = fork();
            if(pid < 0)
            {
                    return -1;
            }
            else if(pid == 0)
            {
                    sleep(10);//让子进程睡的时间长一点,让父进程先退出
                    printf("this is child\n");
            }
            else
            {
                    sleep(5);//这里睡几秒是为了执行后观察父进程退出之前的状态
                    printf("this is parent\n");
            }
            return 0;
    }
    

    当父进程退出之前的状态:此时的父进程和子进程都没有退出:

    当父进程先于子进程退出的时候:父进程因为退出而消失,子进程就变成了一个孤儿进程。

    这就是一个孤儿进程。

    7.进程的优先级

     为什么要有进程的优先级?

             因为进程的功能不同,因此进程对CPU的资源的要求也不同,所以对进程的调度就有了优先级,进程的优先级决定以一个进程的CPU资源的优先级分配权。

             cpu资源分配的先后顺序,就是指进程的优先权。

    使用命令ps -l来查看

    上面的一些列是什么意思呢?

            UID:代表执行者的身份
            PID:代表这个进程的id
            PPID:代表的是父进程的id
            PRI:代表这个进程可被执行的优先级,它的值越小优先级越高
            NI:代表这个进程的nice值。(表示进程可被执行的优先级的修正数据)      

    其实进程的优先级最后等于PRI(New) = PRI(Old)+ nice  (所以当nice的值是负数的时候,优先级越高)。    PRI越小,越早执行。

    修改进程的优先级命令:

            nice:可以指定进程(还没有运行起来的进程)的优先级。(nice -n 你要设置的nice值  程序)nice -n -10  ./test
            renice:也可以指定进程(已经运行起来的进程)的优先级。(renice 你要设置的nice值   -p   进程的id)renice -10 -p 5200

    修改演示:首先编写一个死循环的程序,然后执行这个程序

    nice 对进程的修改:

    renice 对进程的修改:首先将程序执行起来,然后找到这个进程的id,然后用renice来修改优先级。

    其他的概念:

            竞争性:系统的进程数目过多,而CPU只有少量,甚至只有1个,所以进程之间具有竞争的属性,为了高效的完成任务,更合理的竞争相关的资源,所以就有了优先级。
            独立性:一个进程出现问题不会影响其他的进程。多个进程运行期间互不干扰。
            并行:多个进程在多个CPU下分别同时运行,这称之为并行。
            并发:多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进,称之为并发

    环境变量

            概念:环境变量一般是指在操作系统中用来指定操作系统运行环境的一些参数。

    查看环境变量的命令:env.

            环境变量保存操作系统环境相关的一些功能性的数据的变量。
            环境变量具有全局性,就是在哪里都可以找到这个环境变量。

    环境变量的一些操作:

            1.通过main函数的第3个参数来获取环境变量。

    #include<stdio.h>
    #include<unistd.h>
    
    int main(int argc, char *argv[], char *env[])
    {
            int i = 0;
            for(i = 0; env[i] != NULL; i++)
            {
                    printf("%s\n ",env[i]);
            }
        return 0;
    }
    

            2.通过一个全局变量**environ来获取环境变量。

    #include<stdio.h>
    include<unistd.h>
    int main(int argc, char *argv[], char *env[])
    {
       extern char **environ;//存放环境变量的全局变量(因为它没有包含在头文件中,所以要声明)
       int i = 0;
       for(i = 0; environ[i];i++)
       printf("%s \n",environ[i]);
       return 0;
    }
    

            3.getenv() ---》-获取指定的环境变量。(getenv("PATH")).
                              putenv()-----设置一个环境变量

    #include<stdio.h>
    #include<unistd.h>
    #include<stdlib.h>
    int main()
    {
            printf("%s\n", getenv("PATH");//用getenv来访问PATH这个环境变量
            return 0;
    }
    

            4.export (设置一个环境变量)。

            5.set :查看一个环境变量。(它查看的环境变量比env更加的详细)
            6.echo :可以打印一个环境变量,在变量前加$.
            如何干掉一个环境变量:
                 unset:删除一个环境变量(unset 环境变量的名字)

    程序(进程)地址空间:

            首先它是一个线性的地址空间。(进程的虚拟地址空间)
            程序没有地址空间,因为程序在被激活运行起来之后才会有一个运行空间。

            内存地址就是对内存的一个编号,一个地址指向内存的一个位置。

    那我们可以直接访问到物理内存呢?看下面的两段代码,思考为什么?

    zone1.c

    #include<stdio.h>
    #include<unistd.h>
    
    int val = 100;
    
    int main()
    {
            pid_t pid = fork();
            if(pid < 0)
            {
                    return -1;
            }
            else if(pid == 0)
            {
                    printf("child val:%d----%p\n",val);//在子进程中打印出val的值和它的地址
            }
            else
            {
                    printf("parent val:%d----%p\n", val);//在父进程中打印出val的值和他的地址
            }
            return 0;
    }
    

    zone.c

    #include<stdio.h>
    #include<unistd.h>
    
    int val = 100;
    
    int main()
    {
            pid_t pid = fork();
            if(pid < 0)
            {
                    return -1;
            }
            else if(pid == 0)
            {
                    val = 200;//在子进程中修改val的值,然后查看父进程中的val的值是否改变
                    printf("child val:%d----%p\n",val);//在子进程中打印val的值和地址,
            }
            else
            {
                    sleep(3);//这里让父进程先睡三秒,让子进程先执行。
                    printf("parent val:%d----%p\n", val);//在子进程中打印val的值和地址,查看与父进程中的val和地址有什么区别。
            }
            return 0;
    }
    

    这两个代码段执行的结果为: 

    这是就会产生一个问题:为什么在zone的执行结果中,父子进程的变量的值不相同,但是地址为什么相同呢?

            1.变量的内容不一样,代表父进程和子进程输出的变量绝对不是同一个变量

            2.地址一样,代表该地址一定不是物理地址。

    其实进程的地址空间是一个虚拟的地址,并不是物理内存的地址。

    一个虚拟地址如何找到真正的物理地址?

            其实虚拟地址和物理地址直接有一个页表的存在。

            页表:1.记录虚拟地址与物理地址之间的映射关系。
                       2.内存的访问控制(记录了属性的信息,比如:记录了该代码段是只读或者是只写的)。

    页表也是一个结构体。

    什么是虚拟地址空间:
            虚拟地址空间是一个结构体mm_struct结构描述的,结构体的名字叫struct_mm,因此程序地址空间应该叫虚拟地址空间。

    程序的地址空间都是虚拟地址,而不是真正的物理内存地址,而访问虚拟地址空间是通过页表的转换后得到物理内存地址而访问内存的。

    为什么计算机告诉我们每个进程都有4G的地址空间?
            因为计算机知道我们用不了这门多的地址空间,所以我们使用多少就通过页表给我们在物理内存中映射多少地址空间。

    写时复制技术:
            假设有一个全局变量,那么在物理内存中就有一段空间来存放这个全局变量,如果父子进程不修改这个全局变量的话,那么父子进程的虚拟地址空间通过页表都指向同一块物理空间。如果父子进程修改这个全局变量的话,虚拟内存的地址不变,但是在物理内存空间会重新分配一块内存来保存你父子进程修改后的数据,并更新页表,这样也就是为什么你发现父子进程中一个变量它的虚拟地址相同但是值不同原因。

    展开全文
  • fork之后子进程到底复制了父进程什么

    千次阅读 多人点赞 2019-04-18 12:08:23
    fork子进程完全复制进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”(类似mmap的private的方式) 如果父子进程一直对这个页面...

    作者:狂奔的乌龟 
    来源:CSDN 
    原文:https://blog.csdn.net/xy010902100449/article/details/44851453 


     

    #include<stdio.h>
    #include<string.h>
    #include<stdlib.h>
    #include<unistd.h>
     
    void main()
    {
        char str[6]="hello";
     
        pid_t pid=fork();
     
        if(pid==0)
    	{
            str[0]='b';
            printf("子进程中str=%s\n",str);
            printf("子进程中str指向的首地址:%x\n",(unsigned int)str);
        }
        else
    	{
            sleep(1);
            printf("父进程中str=%s\n",str);
            printf("父进程中str指向的首地址:%x\n",(unsigned int)str);
        }
    }
    

    子进程中str=bello
    子进程中str指向的首地址:bfdbfc06
    父进程中str=hello
    父进程中str指向的首地址:bfdbfc06

     

    这里就涉及到物理地址和逻辑地址(或称虚拟地址)的概念。

    从逻辑地址到物理地址的映射称为地址重定向。分为:

    • 静态重定向--在程序装入主存时已经完成了逻辑地址到物理地址和变换,在程序执行期间不会再发生改变。
    • 动态重定向--程序执行期间完成,其实现依赖于硬件地址变换机构,如基址寄存器。

     

    逻辑地址:CPU所生成的地址。CPU产生的逻辑地址被分为 :

    • p (页号) 它包含每个页在物理内存中的基址,用来作为页表的索引;
    • d (页偏移),同基址相结合,用来确定送入内存设备的物理内存地址。

    物理地址:内存单元所看到的地址。

     

    用户程序看不见真正的物理地址。用户只生成逻辑地址,且认为进程的地址空间为0到max。物理地址范围从R+0到R+max,R为基地址,地址映射-将程序地址空间中使用的逻辑地址变换成内存中的物理地址的过程。由内存管理单元(MMU)来完成。

    fork()会产生一个和父进程完全相同的子进程,但子进程在此后多会exec系统调用,出于效率考虑,linux中引入了“写时复制“技术,也就是只有进程空间的各段的内容要发生变化时,才会将父进程的内容复制一份给子进程在fork之后exec之前两个进程用的是相同的物理空间(内存区),子进程的代码段、数据段、堆栈都是指向父进程的物理空间,也就是说,两者的虚拟空间不同,但其对应的物理空间是同一个

    当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间,如果不是因为exec,内核会给子进程的数据段、堆栈段分配相应的物理空间(至此两者有各自的进程空间,互不影响),而代码段继续共享父进程的物理空间(两者的代码完全相同)。而如果是因为exec,由于两者执行的代码不同,子进程的代码段也会分配单独的物理空间。

     

    fork时子进程获得父进程数据空间、堆和栈的复制,所以变量的地址(当然是虚拟地址)也是一样的。

    每个进程都有自己的虚拟地址空间,不同进程的相同的虚拟地址显然可以对应不同的物理地址。因此地址相同(虚拟地址)而值不同没什么奇怪。


    “写时复制”的具体过程是这样的:

    fork子进程完全复制父进程的栈空间,也复制了页表,但没有复制物理页面,所以这时虚拟地址相同,物理地址也相同,但是会把父子共享的页面标记为“只读”(类似mmap的private的方式)

    如果父子进程一直对这个页面是同一个页面,直到其中任何一个进程要对共享的页面“写操作”,这时内核会复制一个物理页面给这个进程使用,同时修改页表。而把原来的只读页面标记为“可写”,留给另外一个进程使用。

    这就是所谓的“写时复制”。

     

     

    正因为fork采用了这种写时复制的机制,所以fork出来子进程之后,父子进程哪个先调度呢?

    • 内核一般会先调度子进程,因为很多情况下子进程是要马上执行exec,会清空栈、堆。这些和父进程共享的空间,加载新的代码段。这就避免了“写时复制”拷贝共享页面的机会。
    • 如果父进程先调度很可能写共享页面,会产生“写时复制”的无用功。所以,一般是子进程先调度。

     

    假定父进程malloc的指针指向0x12345678, fork 后,子进程中的指针也是指向0x12345678,但是这两个地址都是虚拟内存地址 (virtual memory),经过内存地址转换后所对应的 物理地址是不一样的。所以两个进城中的这两个地址相互之间没有任何关系。


    (注:但实际上,linux为了提高 fork 的效率,采用了 copy-on-write 技术,fork后,这两个虚拟地址实际上指向相同的物理地址(内存页),只有任何一个进程试图修改这个虚拟地址里的内容前,两个虚拟地址才会指向不同的物理地址(新的物理地址的内容从原物理地址中复制得到))

     

    2、exec家族

    exec家族一共有六个函数,分别是:

    (1)int execl(const char *path, const char *arg, ......);
    (2)int execle(const char *path, const char *arg, ...... , char * const envp[]);
    (3)int execv(const char *path, char *const argv[]);
    (4)int execve(const char *filename, char *const argv[], char *const envp[]);
    (5)int execvp(const char *file, char * const argv[]);
    (6)int execlp(const char *file, const char *arg, ......);</span>
    

    其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。

     

    exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。

    与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。
     

     

    展开全文
  • php多进程-概念

    千次阅读 2019-06-17 20:55:39
    php多进程-概念 扩展 pcntl和 posix php多进程只能运行在php CLI(命令行)环境下,在web服务器环境下,会出现无法预期的结果,请慎用! 线程和进程 1、进程下的多线程是共享该进程下的全部内存,于是就会存在...
  • 返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程PID;否则,出错返回-1。 fork()函数将运行着的程序分成2个(几乎)完全一样的进程,每个进程都启动一个从代码的同一位置开始执行的线程。这两...
  • Linux 系统编程 -进程概念

    万次阅读 多人点赞 2021-06-07 20:16:56
    Linux系统编程-进程篇冯诺依曼体系结构冯诺依曼的两个重要思想当代计算机的三级缓存操作系统操作系统的概念操作系统的组成操作系统作用Linux下的操作系统体系进程进程概念进程特性进程的组成进程与程序区别进程控制...
  • 进程概述 当一个可执行程序在现代系统上运行时,操作系统会提供一种...这些假象是通过进程概念来实现的。  进程是操作系统对一个正在运行的程序的一种抽象。在一个系统上可以同时运行多个进程,而每个进程都好像在
  • 深入理解Linux进程概念

    千次阅读 2021-03-05 20:46:18
    Linux进程概念 冯·诺依曼体系结构: 冯诺依曼体系结构作为现代计算机硬件体系结构,规定了现代计算机应该具有哪些硬件单元 输入设备 :键盘,鼠标,图像采集设备 ,声音采集设备 输出设备:显示器 存储器:...
  • 假定父进程malloc的指针指向0x12345678, fork 后,子进程中的指针也是指向0x12345678,但是这两个地址都是虚拟内存地址 (virtual memory),经过内存地址转换后所对应的 物理地址是不一样的。所以两个进城中的这两个...
  • 进程概念的引入进程的概念: 进程是程序的一次执行; 进程是可以和别的计算器并发执行的计算; 可定义为一个数据结构及能在其上进行操作的一个程序; 一个程序及其数据在处理机上顺序执行时所发生的活动; 是程序在...
  • 进程通信概念进程通信方式

    万次阅读 2017-05-08 19:11:01
    进程通信(IPC,Inter-Porcess Communcation)是进程进行通信和同步的机制。IPC提供两个基本操作: 发送(send message) 接收(receive message) 进程通信流程: 在通信进程之间建立通信链路 通过send/receive交换...
  • Linux:进程概念和进程控制

    千次阅读 2021-10-23 22:46:21
    kill命令对僵尸进程没用. 孤儿进程不占据当前程序运行的会话,在后台运行(当前会话能打指令) 1号进程(init)是负责任的进程,所以孤儿进程退出不会成为僵尸进程,可以被kill掉 孤儿进程设置了自己的会话空间后成为...
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    Files.copy():复制文件。 Files.move():移动文件。 Files.size():查看文件个数。 Files.read():读取文件。 Files.write():写入文件。 二、容器 18. java 容器都有哪些? 常用容器的图录: 19. Collection 和 ...
  • 操作系统进程相关知识学习
  • (十九)进程——概念引入

    千次阅读 2017-01-05 11:34:04
    进程是一个动态的概念,也是操作系统分配资源的最小单位。  我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。虽然在不同的操作系统中对...
  • 进程的基本概念进程控制

    千次阅读 2016-10-31 19:03:06
    一、 进程控制理论  什么是进程?什么是多进程进程的分类?  进程和程序的区别?  linux进程管理,有哪些管理工具?作用?  进程的三态是什么?进程的结构有哪些?  进程的调度算法有哪些?什么是优先级...
  • Linux-进程概念

    千次阅读 2018-03-19 08:57:12
    - 内核(进程管理,内存管理,文件管理,驱动管理) - 其他程序(例如函数库,shell等) 操作系统是管管理的软件 用户操作 → 用户操作接口(shell,lib,指令等) → 操作系统 → 驱动程序 → 硬件 操作系统管理...
  • 进程管理——基础概念

    千次阅读 2017-07-01 11:50:16
    简介 程序是放在磁盘上的文件,当这个文件可以运行起来干某件事时候,它就成了进程了,当然一个...线程是进程的下一代,当一个进程需要同时干某件事情时候,就需要有多个线程同时工作,来完成一个进程,比如说听音乐
  • 一文读懂Linux进程进程组、会话、僵尸

    千次阅读 多人点赞 2020-02-24 08:21:00
    数据的复制采用的是所谓的写时复制(copy on writte),即只有在任一进程(父进程或子进程)对数据执行了写操作时,复制才会发生(先是缺页中断,然后操作系统给子进程分配内存并复制进程的数据)。 如果我们在...
  • 入门学习Linux常用必会60个命令实例详解doc/txt

    千次下载 热门讨论 2011-06-09 00:08:45
    因为Linux与Windows不同,其后台运行着许多进程,所以强制关机可能会导致进程的数据丢失,使系统处于不稳定的状态,甚至在有的系统中会损坏硬件设备(硬盘)。在系统关机前使用 shutdown命令,系统管理员会通知所有...
  • 进程有这么多的种类,那么进程之间定是有相关性的,而这些有关联性的进程又是如何产生的,如何衍生的? 就比如我们启动了终端,就是启动了一个 bash 进程,我们可以在 bash 中再输入 bash 则会再启动一个 bash 的...
  • 汽车软件RTOS概念——进程,线程,任务

    千次阅读 多人点赞 2020-12-15 22:00:35
    一般看《计算机操作系统》的书籍,都会有进程(Process),线程(Thread)的概念。但是在嵌入式RTOS里面,比如应用于汽车软件的OSEK/VDX Operating System Specification 2.2.3规范里没有这两个概念,有的是任务...
  • linux进程fork——写时复制

    千次阅读 2018-08-12 15:45:41
    fork到底复制了父进程的哪些资源? 我们来看一个例子 #include&amp;amp;amp;lt;stdio.h&amp;amp;amp;gt; #include&amp;amp;amp;lt;stdlib.h&amp;amp;amp;gt; #include&amp;amp;amp;lt;...
  • MySQL 面试题

    万次阅读 多人点赞 2019-09-02 16:03:33
    MySQL 面试题 MySQL 涉及的内容非常非常非常多,所以面试题也容易写的杂乱。当年,我们记着几个一定要掌握的重心: 重点的题目添加了【重点】前缀。 索引。 ...因为 MySQL 还会有部分内容和运维相关度比较高,所以...
  • Linux

    千次阅读 多人点赞 2020-02-11 21:22:11
    kill -9 2868 强制杀死进程 6.5 管道 | 作用 管道是 Linux 命令中重要的一个概念,其作用是将一个命令的输出用作另一个命令的输入。 用法 ls --help | more 分页查询帮助信息 ps –ef | grep java 查询名称中包含 ...
  • 一、什么是进程 从用户角度来说,进程是程序的一次动态执行过程;从操作系统的核心来看,进程是操作系统分配的内存、CPU时间片等资源的基本单位。每一个进程都有自己独立的地址空间与执行状态。 二、进程数据结构 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 145,763
精华内容 58,305
关键字:

复制进程概念