linux进程

    xiaoxiao2021-03-31  43

    一、程序和进程的区别

    C、C++、Java等语言编写的源程序经相应的编译器编译成可执行文件后,提交给计算机处理器运行。应用程序的运行状态称为进程。进程与应用程序的区别在于应用程序作为一个静态文件存储在计算机系统的硬盘等存储空间中,而进程则是处于动态条件下由操作系统维护的资源管理实体。进程和程序是有本质区别的:程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念;而进程是一个动态的概念,它是程序执行的过程,包括了动态创建、调度和消亡的整个过程;它是程序执行和资源管理的最小单位。

    二、进程和线程

    线程和进程是另一对有意义的概念,主要区别和联系如下:

    进程是操作系统进行资源分配的基本单位,拥有完整的进程空间。进行系统资源分配的时候,除了CPU资源之外,不会给线程分配独立的资源,线程所需要的资源需要共享。

    线程是进程的一部分,如果没有进行显示的线程分配,可以认为进程是单线程的;如果进程中建立了线程,则可认为系统是多线程的。

    多线程和多进程是两种不同的概念。多线程与多进程有不同的资源共享方式。

    进程有进程控制块PCB,系统通过PCB对进程进行调度。进程有线程控制块TCP,但TCB所表示的状态比PCB要少的多。

    三、Linux进程

    实际上,当计算机开机的时候,内核(kernel)只建立了一个init进程。Linux内核并不提供直接建立新进程的系统调用。剩下的所有进程都是init进程通过fork机制建立的。新的进程要通过老的进程复制自身得到,这就是fork。fork是一个系统调用。进程存活于内存中。每个进程都在内存中分配有属于自己的一片空间(address space)。当进程fork的时候,Linux在内存中开辟出一片新的内存空间给新的进程,并将老的进程空间中的内容复制到新的空间中,此后两个进程同时运行。

    子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的副本,这意味着父子进程间不共享这些存储空间。

    四、进程标识符

    在Linux中最主要的进程标识: 进程号(PID,Process Idenity Number) 父进程号(PPID,parent process ID)。 其中PID惟一地标识一个进程,内核通过这个标识符来识别不同的进程,用户程序通过PID对进程发号施令。 PID和PPID都是是32位的无符号整数。

    老进程成为新进程的父进程(parentprocess),而相应的,新进程就是老的进程的子进程(child process)。一个进程除了有一个PID之外,还会有一个PPID(parentPID)来存储的父进程PID。如果我们循着PPID不断向上追溯的话,总会发现其源头是init进程。所以说,所有的进程也构成一个以init为根的树状结构。

     写时复制技术内核只为新生成的子进程创建虚拟空间结构,它们来复制于父进程的虚拟究竟结构,但是不为这些段分配物理内存,它们共享父进程的物理空间,当父子进程中有更改相应段的行为发生时,再为子进程相应的段分配物理空间。

    五、任务数据结构

    内核程序通过进程表对进程进行管理,每个进程在进程表中占有一项。在Linux系统中,进程表项是一个task_struct任务结构指针。任务数据结构定义在头文件include/linux/sched.h中。有些书上称其为进程控制块PCB(Process Control Block)或者进程描述符PD(Processor Descriptor)。其中保存着用于控制和管理进程的所有信息。主要包括进程当前运行的状态信息,信号,进程号,父进程号,运行时间累计值,正在使用的文件和本任务的局部描述符以及任务状态段信息。该结构每个字段的含义如下所示。

    当一个进程在执行时,CPU的所有寄存器中的值,进程的状态以及堆栈中的内容被称为该进程的上下文。当内核需要切换(switch)至另一个进程时,它就需要保存当前进程的所有状态,也即保存当前进程的上下文,以便在再次执行该进程时,能够恢复到切换时的状态执行下去。在Linux中,当前进程上下文均保存在进程的任务数据结构task_struct中。在发生中断时,内核就在被中断进程的上下文中,在内核状态下执行中断服务例程。但同时会保留所有需要用到的资源,以便中断服务结束时能恢复被中断进程的执行。

    六、进程操作相关函数

    getpid返回当前进程标识,getppid返回父进程标识。 用法:   #include <sys/types.h> #include <unistd.h>

    pid_tgetpid(void); pid_t getppid(void);

    1、创建进程的函数原型

    pid_t forkvoid);

    pid_t是一个宏定义,其实质是int被定义在#include<sys/types.h>中)

    返回值:若成功调用一次则返回两个值,子进程返回0父进程返回子进程ID;否则,出错返回-1

    注意:

         一般来说,fork之后父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。

    pid_t vforkvoid);

    fork()与vfock()都是创建一个进程,那他们有什么区别呢?总结有以下三点区别:  1.  fork  ():子进程拷贝父进程的数据段,代码段      vfork ):子进程与父进程共享数据段  2.  fork ()父子进程的执行次序不确定      vfork 保证子进程先运行,在调用execexit之前与父进程数据是共享的,在它调用execexit之后父进程才可能被调度运行。  3.  vfork ()保证子进程先运行,在她调用execexit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

    为什么会有vfork,因为以前的fork 很傻, 它创建一个子进程时,将会创建一个新的地址 空间,并且拷贝父进程的资源,而往往在子进程中会执行exec 调用,这样,前面的拷贝工 作就是白费力气了,这种情况下,聪明的人就想出了vfork,它产生的子进程刚开始暂时与 父进程共享地址空间(其实就是线程的概念了),因为这时候子进程在父进程的地址空间中 运行,所以子进程不能进行写操作,并且在儿子 霸占”着老子的房子时候,要委屈老子一 下了,让他在外面歇着(阻塞),一旦儿子执行了exec 或者exit 后,相 于儿子买了自己的 房子了,这时候就相 于分家了。

    2、exec函数族

    fork函数创建子进程后,子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程完全由新程序代换,而新程序则从其 main函数开始执行。因为调用exec并不创建新进程,所以前后的进程ID并未改变。exec只是用另一个新程序替换了当前进程的正文、数据、堆和栈段。有六种不同的exec函数可供使用,它们常常被统称为exec函数。这些exec函数都是UNIX进程控制原语。用fork可以创建新进程,用exec可以执行新的程序。

    exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段、和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。 

    3、wait和waitpid

    wait函数:调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。

    waitpid函数:功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞)。

    wait函数

    pid_t  wait(int *status);

    函数参数:

    status是一个整型指针,指向的对象用来保存子进程退出时的状态。

    A.status若为空,表示忽略子进程退出时的状态

    B.status若不为空,表示保存子进程退出时的状态

    子进程的结束状态可由Linux中一些特定的宏来测定。

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

    4、exit

    exit是一个库函数,exit(1)表示发生错误后退出程序,exit(0)表示正常退出。在stdlib.h中exit函数是这样子定义的:void exit(int status)。这个系统调用是用来终止一个进程的,无论在程序中的什么位置,只要执行exit,进程就会从终止进程的运行。讲到exit这个系统调用,就要提及另外一个系统调用,_exit,_exit()函数位于unistd.h中,相比于exit(),_exit()函数的功能最为简单,直接终止进程的运行,释放其所使用的内存空间,并销毁在内存中的数据结构,而exit()在于在进程退出之前要检查文件的状态,将文件缓冲区中的内容写回文件。

    exit函数和_exit函数有如下区别:

    1> exit函数在ANSIC中说明的,而_exit函数是在POSIX标准中说明

    2> exit函数将终止调用进程,在退出程序之前所有文件被关闭,标准输入输出的缓冲区被清空,并执行在atexit注册的回调函数;_exit函数终止调用进程,但不关闭文件,不清除标准输入输出的缓冲区,也不调用在atexit注册的回调函数。

    七、三种启动新进程方法的比较

    首先是最简单的system函数,它需要启动新的shell并在新的shell是执行子进程,所以对环境的依赖较大,而且效率也不高。同时system函数要等待子进程的返回才能执行下面的语句。

    exec系统函数是用新的进程来替换原先的进程,效率较高,但是它不会返回到原先的进程,也就是说在exec函数后面的所以代码都不会被执行,除非exec调用失败。然而exec启动的新进程继承了原进程的许多特性,在原进程中已打开的文件描述符在新进程中仍将保持打开,但需要注意,任何在原进程中已打开的目录流都将在新进程中被关闭。

    fork则是用当前的进程来复制出一个新的进程,新进程与原进程一模一样,执行的代码也完全相同,但新进程有自己的数据空间、环境变量和文件描述符,我们通常根据fork函数的返回值来确定当前的进程是子进程还是父进程,即它并不像exec那样并不返回,而是返回一个pid_t的值用于判断,我们还可以继续执行fork后面的代码。感觉用fork与exec系列函数就能创建很多需的进程。

    八、终止进程的五种方法

    从main函数返回:从return返回,执行完毕退出调用exit:C函数库,实际上也是调用系统调用_exit完成的,在任何一个函数调用exit函数都可使得进程撤销调用_exit:系统调用调用abort:调用abort()函数使得进程终止,实际上该函数是产生一个SIGABRT信号,由信号终止:发送一些信号如SINGINT等信号

    #include <unistd.h> #include <syspes.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <math.h> int main(void) { pid_t child; /* 创建子进程 */ if((child=fork())==-1) { printf("Fork Error : %s\n", strerror(errno)); exit(1); } else { if(child==0) // 子进程 { printf("the child process is run\n"); sleep(1); //子进程睡眠一秒,但并没有去运行父进程 printf("I am the child: %d\n", getpid()); exit(0); } else //父进程 { wait(NULL); //等到子进程退出,父进程才会运行 printf("the father process is run\n"); printf("I am the father:%d\n",getpid()); return 0; } } }

    转载请注明原文地址: https://ju.6miu.com/read-665341.html

    最新回复(0)