操作系统——Linux进程管理

本文详细讲解了Linux进程的基本概念,包括进程与程序的关系、进程分类、查看进程方法,重点介绍了进程创建(fork与vfork)、正常与异常终止、子进程回收以及wait系列函数。还展示了如何通过实例操作和函数实现孤儿进程和僵尸进程的管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 <stdio.h>

            2 #include <unistd.h>

            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 <stdio.h>

#include <sys/types.h>

#include <unistd.h>

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 <stdio.h>

#include <sys/types.h>

#include <unistd.h>

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 <stdio.h>

                    #include <stdlib.h>

                    #include <unistd.h>

                    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 <stdio.h>

                #include <unistd.h>

                #include <sys/types.h>

                #include <sys/wait.h>

                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");

                }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

xiaoyu1381

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值