• 操作系统——Linux进程管理


    Linux进程管理

    一、进程的基本概念

        1、进程与程序

            程序是存储在磁盘上的可执行文件,程序被加载到内存中开始运行时叫做进程

            一个程序可以被多次加载生成多个进程,进程就是处于活动状态的计算机程序

        2、进程的分类

            进程一般分为三大类:交互进程、批处理进程、守护进程

        3、查看进程

            简单模式:ps         显示当前用户有终端控制进程简单信息

            列表模式:ps -auxw   显示所有进程的详细信息

                    a     所有用户的有终端控制的进程

                    x     无终端控制的进程

                    u     显示所有进程的详细信息

                    w     以更大的列宽显示

                        USER    进程的属主用户名

                        PID     进程号

                        %CPU    CPU的使用率

                        %MEM    内存的使用率

                        VSZ     虚拟内存使用的字节数

                        RSS     物理内存使用的字节数

                        TTY     终端设备号  '?'表示无终端控制

                        STAT    进程的状态

                                O   就绪态              等待被调用

                                R   运行态              Linux系统中没有O,就绪也用R来表示

                                S   可被唤醒的睡眠态     例如系统中断、获取资源、收到信号等都可以唤醒进入运行态

                                D   不可被唤醒的睡眠态   只能被系统唤醒

                                T   暂停态              收到SIGTSTP信号进入暂停态,收到SIGCONT信号转回运行态

                                X   死亡态

                                Z   僵尸态(将死态)      

                                N   低优先级

                                <   高优先级

                                l   多线程进程

                                s   进程的领导者        

                        START   进程的启动时间

                        TIME    进程的运行时间

                        COMMAND 启动进程的命令

        4、父进程、子进程、孤儿进程、僵尸进程

            一个进程可以被另一个进程创建,创建者叫做父进程,

            被创建者叫子进程,子进程被父进程创建后会在操作系统的调度下同时运行

            当子进程先于父进程结束,死前子进程会向父进程发送信号SIGCHLD,此时父进程应该去回收子进程的相关资源

            孤儿进程:父进程先于子进程结束,子进程就变成了孤儿进程,孤儿进程会被孤儿院(init守护进程)领养

            init就是孤儿进程的父进程

            僵尸进程:该进程已死亡,但是它的父进程没有立即回收它的相关资源,该进程就进入僵尸态

        5、进程标识符

            每个进程都有一个非负整数表示唯一标识,即进程ID/PID

            进程ID在任意时刻都是唯一的,但是可以重新使用,进程一旦结束,它的进程ID就会被系统回收,过一段时间后再重新分配给其他新创建

            的进程使用(延时重用)

            pid_t getpid(void);

            功能:返回调用者的进程ID

            pid_t getppid(void);

            功能:返回父进程的ID

    二、创建进程

         pid_t fork(void);

         功能:创建子进程

         返回值:一次调用两次返回,子进程返回0,父进程返回子进程的ID,当进程数量超过系统的限制时会创建失败,返回-1

         1、通过fork创建的子进程会拷贝父进程(数据段、bss段、堆、栈、I/O缓冲区),与父进程共享代码段、子进程会继承父进程的

         信号处理方式

         2、fork函数调用后父子进程各自独立运行,谁先返回不确定,但是可以通过睡眠确定让哪个进程先执行

         3、通过fork创建的子进程可以共享父进程的文件描述符

         4、可以根据返回值的不同让父子进程进入不同的分支,执行不同的代码

         练习1:为进程创建4个子进程,再为这4个子进程,分别创2个子进程

         解题思路:子程序创建后,执行完任务后就休眠,这样不会被其他的fork影响

                1 #include

                2 #include

                3

                4 int main(int argc,const char* argv[])

                5 {

                6     printf("我是进程%u\n",getpid());

                7     for(int i = 0;i<4;i++)

                8     {

                9         if(0 == fork())

                10         {

                11             printf("我是进程%u,我的父进程是%u\n",getpid(),getppid());

                12             for(int j = 0;j<2;j++)

                13             {

                14                 if(0 == fork())

                15                 {

                16                     printf("我是进程%u,我的父进程是%u\n",getpid(),getppid());

                17                     pause;

                18                 }

                19             }

                20             pause();

                21         }

                22     }

                23     pause();

                24 }  




     

        pid_t vfork(void);

        功能:以加载可执行文件的方式来创建子进程

        返回值:子进程返回0,父进程返回子进程的ID

        注意:vfork创建的子进程一定先返回,此时子进程并没有创建成功,需要加载一个可执行文件替换当前子进程所有资源,当替换完成后子进程

        才算创建成功,此刻父进程才返回

        使用exec  系列函数让子进程加载可执行文件

        extern char **environ;

        int execl(const char *path, const char *arg, ...

                            /* (char  *) NULL */);

        path:可执行文件的路径

        arg:命令行参数,个数不定,由实际的可执行文件所需命令行参数决定

            一般第一个是可执行文件的名字,至少有一个,一定要以NULL结尾

        int execlp(const char *file, const char *arg, ...

                            /* (char  *) NULL */);

        file:可执行文件名字

        arg:命令行参数,同上

        注意:会去系统默认路径PATH 指定的路径下加载file

        int execle(const char *path, const char *arg, ...

                            /*, (char *) NULL, char * const envp[] */);

        path:可执行文件的路径

        arg:命令行参数,同上

        envp:环境变量表,父进程可以在加载子进程时把环境变量表传递给子进程

        int execv(const char *path, char *const argv[]);

        path:可执行文件的路径

        argv:命令行参数数组,最后以NULL结尾

        int execvp(const char *file, char *const argv[]);

        file:可执行文件名字

        argv:命令行参数数组,同上

        注意:也是根据PATH的路径加载file

        int execvpe(const char *file, char *const argv[],char *const envp[]);

        file:可执行文件名字

        argv:命令行参数数组,同上

        envp:环境变量表

        注意:也是根据PATH的路径加载file

        exec系列函数正常情况下是不会返回的,当子进程加载失败时才会返回-1

        虽然以vfork、exec系列函数创建加载的子进程不会继承父进程的信号处理函数,但是能继承父进程的信号屏蔽集

    #include

    #include

    #include

    int main(int argc,const char* argv[],char** environ)

    {

        pid_t pid = vfork();

        if(0 == pid)

        {

            //execlp("hello","hello","-l",NULL);

            char* argv[] = {"hello","-a",NULL};

            execvpe("hello",argv,environ);

            printf("我还在!\n");

        }

        else

        {

            printf("我是进程%u,我的子进程是%u\n",getpid(),pid);  

        }

    }

        练习2:实现出孤儿进程和僵尸进程 ps查看

    #include

    #include

    #include

    int main(int argc,const char* argv[])

    {

        /*

        //  孤儿进程

        if(fork())

        {

            printf("我是父进程%u\n",getpid());

            sleep(3);

            printf("我要死啦!\n");

        }

        else

        {

            for(;;)

            {

                printf("我是子进程%u,我的父进程是%u\n",

                    getpid(),getppid());

                sleep(1);

            }

        }

        */

        //  僵尸进程

        if(fork())

        {

            for(;;)

            {

                printf("我是父进程%u\n",getpid());

                sleep(1);

            }

        }

        else

        {

            printf("我是子进程%u,我的父进程是%u\n",

                getpid(),getppid());

            sleep(2);

            printf("我要完了\n");

        }

    }

    三、进程的正常退出

        1、在main函数中执行 return n;该返回值n可以被父进程获取,几乎与exit等价

        2、进程调用了exit函数,该函数是C标准库中的函数

             void exit(int status);

             功能:在任何时间、地点调用该函数都可以立即结束进程

             status:结束状态码(EXIT_SUCCESS/EXIT_FAILURE),与main函数中return的返回值效果是一样的

             返回值:该函数不会返回

             进程退出前要完成:

                1、先调用事先通过atexit\on_exit函数注册的函数,如果都注册了执行顺序与注册顺序相反

                int atexit(void (*function)(void));

                功能:向内核注册一个进程结束前必须调用的函数

                int on_exit(void (*function)(int , void *), void *arg);

                功能:向内核注册一个进程结束前必须调用的函数

                arg:会在调用function时传给它

                2、冲刷并关闭所有打开状态的标准IO流

                3、底层继续调用_Exit/_exit函数

                        #include

                        #include

                        #include

                        void atexit_fp(void)

                        {

                            printf("我要死啦!\n");  

                        }

                        void on_exit_fp(int num,void* ptr)

                        {

                            if(EXIT_SUCCESS == num)

                            {

                                printf("寿终正寝!\n");  

                            }

                            else

                            {

                                printf("有人%d想搞我,%s\n",num,(char*)ptr);  

                            }

                        }

                        int main(int argc,const char* argv[])

                        {

                            on_exit(on_exit_fp,"呵呵");

                            atexit(atexit_fp);

                            printf("我还活着!\n");

                            sleep(3);

                            exit(-10);

                        //  return 0;

                            printf("----\n");

                        }

        3、调用_Exit/_exit函数

            void _exit(int status);

            功能:结束进程,由系统提供的

            void _Exit(int status);

            功能:结束进程,由标准库提供的

            1、它们的参数会被父进程获取到

            2、进程结束前会关闭所有处于打开状态的文件描述符

            3、向父进程发生信号SIGCHLD

            4、该函数也不会返回

        4、进程的最后一个线程执行了return返回语句

        5、进程的最后一个线程执行了pthread_exit函数

    四、进程的异常终止

        1、进程调用了about函数,产生SIGABRT(6)信号

        2、进程接收到某些信号,可以是其他进程发送的,也可能是自己的错误导致的

        3、进程的最后一个线程接收到"取消"请求操作,并响应

    这三种方式结束进程,它的父进程都无法获取结束状态码,因此叫做异常终止

    注意:无论进程是如何结束的,它们最后都会执行同一段代码,会关闭所有打开的文件,并释放所有的内存


     

    五、子进程的回收

        对于子进程的结束而言,都希望父进程能够知道并作出一定的反应,通过wait、waitpid函数可以知道子进程是如何结束的以及它的结束状态码

        pid_t wait(int *status);

        功能:等待子进程结束,并获取结束状态码

        status:输出型参数,接收结束状态码

        返回值:结束的子进程的ID

            1、如果所有子进程都还在运行,则阻塞

            2、如果有一个子进程结束,立即返回该进程的结束状态码和ID

            3、如果没有子进程返回-1

            WIFEXITED(status)

                判断进程是否是正常结束,如果是返回真

            WEXITSTATUS(status)

                如果进程是正常结束的,可以获取到正确的结束状态码

            WIFSIGNALED(status)

                判断进程是否是异常,如果是返回真

            WTERMSIG(status)

                如果进程是异常结束的,可以获取到杀死进程的信号

            WIFSTOPPED(status)

            WSTOPSIG(status)

            WIFCONTINUED(status)


     

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

        功能:等待回收指定的某个或某些进程结束

        pid:

            >0  等待该进程结束

            0   等待同组的任意进程结束

            -1  等待任意进程结束、功能与wait等价

            <-1 等待abs(pid)进程组中的任意进程结束

        status:输出型参数,接收结束状态码

        options:

             WNOHANG    非阻塞模式,如果当前没有子进程结束,则立即返回0

             WUNTRACED  如果有子进程处于暂停态,返回该进程的状态

             WCONTINUED 如果有子进程从暂停态转为继续运行,返回该子进程的状态

             WIFSTOPPED(status)

                        判断子进程是否转为暂停态,是返回真

             WSTOPSIG(status)

                        获取导致子进程进入暂停态的信号

             WIFCONTINUED(status)

                        判断子进程是否由暂停转为继续,是返回真

            int system(const char *command);

            功能:通过创建子进程去执行一个可执行文件

            返回值:子进程结束后才返回

            注意:该函数底层调用了fork、vfork、exec函数、waitpid函数

            练习1:综合进程管理知识点,实现一个system

                    #include

                    #include

                    #include

                    #include

                    int _system(const char* command)

                    {

                        pid_t pid = vfork();

                        if(0 == pid)

                        {

                            return execlp(command,command,NULL);    

                        }

                        else

                        {

                            int status = -1;

                            waitpid(pid,&status,0);

                            return status;

                        }

                    }

                    int main(int argc,const char* argv[])

                    {

                        _system("ls");

                        printf("end!\n");

                    }

  • 相关阅读:
    Python 数据可视化 boxplot
    Vue.js设计与实现
    [附源码]SSM计算机毕业设计拾穗在线培训考试系统JAVA
    黑暗爆炸 #1059. [ZJOI2007]矩阵游戏
    SIFT算法原理
    面向对象重写理解 求值策略 -共享对象调用 面向对象原则
    Android App开机启动
    (212)Verilog HDL:四位移位寄存器和递减计数器
    【小题练手】---Java基础
    Java中实现线程等待和唤醒总结
  • 原文地址:https://blog.csdn.net/xiaoyu1381/article/details/126663513