Linux进程管理

目录

前言

一、进程的基本概念

二、进程的创建

使用fork()函数 

使用vfork()函数 

比较fork()和vfork()

三、进程的退出 

 进程的正常退出

进程的异常终止 

四、监控子进程 


前言

Linux进程是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

二、进程的创建

使用fork()函数 

pid_t fork(void);
功能:创建子进程
返回值:一次调用两次返回,子进程返回0,父进程返回子进程的ID,当进程数量超过系统的限制时会创建失败,返回-1
  • 通过fork创建的子进程会拷贝父进程(数据段、bss段、堆、栈、I/O缓冲区),与父进程共享代码段、子进程会继承父进程的信号处理方式
  • fork函数调用后父子进程各自独立运行,谁先返回不确定,但是可以通过睡眠确定让哪个进程先执行
  • 通过fork创建的子进程可以共享父进程的文件描述符
  • 可以根据返回值的不同让父子进程进入不同的分支,执行不同的代码  

注意:
        1.Fork操作的影响:当一个进程调用fork()创建子进程时,操作系统会复制当前进程的所有资源(包括代码段、数据段、堆栈  等),生成一个新的子进程。这个子进程会从fork()调用后的位置开始执行代码。
        2.并发执行问题:如果在父进程或其他子进程调用fork()创建新的子进程时,此时正在执行fork()操作的进程可能会被影响,因为fork()会复制当前进程的状态,包括指令执行位置等。这可能导致竞争条件或意外的行为。

下面是一个使用fork()创建子进程的简单示例:

#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <sys/wait.h>  
  
int main() {  
    pid_t pid;  
  
    // 使用fork()创建一个新进程  
    pid = fork();  
  
    if (pid == -1) {  
        // fork失败  
        perror("fork failed");  
        exit(EXIT_FAILURE);  
    } else if (pid == 0) {  
        // 子进程代码  
        printf("This is the child process. PID = %d\n", getpid());  
        // 子进程可以执行一些任务...  
        // 然后退出  
        exit(EXIT_SUCCESS);  
    } else {  
        // 父进程代码  
        int status;  
        printf("This is the parent process. Child PID = %d\n", pid);  
        // 父进程可以继续执行其他任务...  
  
        // 等待子进程结束  
        if (waitpid(pid, &status, 0) == -1) {  
            // 等待子进程失败  
            perror("waitpid failed");  
            exit(EXIT_FAILURE);  
        }  
  
        // 检查子进程的退出状态  
        if (WIFEXITED(status)) {  
            printf("Child exited with status %d\n", WEXITSTATUS(status));  
        } else if (WIFSIGNALED(status)) {  
            printf("Child killed by signal %d\n", WTERMSIG(status));  
        }  
  
        // 父进程继续执行其他任务...  
    }  
  
    return 0;  
}

在这个示例中,父进程通过fork()创建了一个子进程。在fork()调用之后,父进程和子进程都会从fork()调用之后的那条指令开始执行,但是它们会分别拥有各自的执行路径。通过fork()的返回值,可以区分当前是在父进程中还是在子进程中执行代码。在父进程中,fork()返回子进程的PID;在子进程中,fork()返回0。

父进程通过waitpid()等待子进程结束,并获取子进程的退出状态。这是一个好习惯,因为它可以防止产生僵尸进程(即已经结束但父进程尚未通过wait()waitpid()获取其退出状态的进程)。

使用vfork()函数 

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:可执行文件名字(环境变量PATH中可执行文件)
argv:命令行参数数组,同上
envp:环境变量表
注意:也是根据PATH的路径加载file

注意:exec系列函数正常情况下是不会返回的,当子进程加载失败时才会返回-1。虽然通过vfork、exec系列函数创建加载的子进程不会继承父进程的信号处理函数,但是能继承父进程的信号屏蔽集

下面是一个使用vfork()创建子进程的简单示例: 

#include <unistd.h>  
#include <sys/types.h>  
#include <sys/wait.h>  
#include <stdio.h>  
#include <stdlib.h>  
  
int main() {  
    pid_t pid;  
  
    // 使用vfork()创建一个新进程  
    pid = vfork();  
  
    if (pid == -1) {  
        // vfork失败  
        perror("vfork failed");  
        exit(EXIT_FAILURE);  
    } else if (pid == 0) {  
        // 子进程代码  
        // 注意:在调用exec()之前,子进程应避免任何可能影响父进程的操作  
        execlp("ls", "ls", "-l", (char *)NULL);  
        // 如果execlp失败,则子进程应退出  
        // 注意:这里使用_exit()而不是exit(),因为exit()会调用清理函数,这些函数可能会与父进程共享的地址空间交互  
        _exit(EXIT_FAILURE);  
    } else {  
        // 父进程代码  
        int status;  
        // 父进程等待子进程结束  
        if (waitpid(pid, &status, 0) == -1) {  
            // 等待子进程失败  
            perror("waitpid failed");  
            exit(EXIT_FAILURE);  
        }  
          
        // 检查子进程的退出状态  
        if (WIFEXITED(status)) {  
            printf("Child exited with status %d\n", WEXITSTATUS(status));  
        } else if (WIFSIGNALED(status)) {  
            printf("Child killed by signal %d\n", WTERMSIG(status));  
        }  
  
        // 父进程继续执行其他任务...  
    }  
  
    return 0;  
}

比较fork()和vfork()

  • 用途:fork() 用于创建一个新的进程,该进程可以独立于父进程运行,或者执行与父进程不同的任务。而 vfork() 主要用于创建一个新进程来执行一个新的程序(即exec()系列函数)。
  • 地址空间:fork() 会为新进程创建独立的地址空间,而 vfork() 的子进程与父进程共享地址空间(直到exec()或exit()被调用)。
  • 性能:vfork()通常比 fork() 更快,因为它避免了为新进程创建新的地址空间的开销。但是,这种性能优势是以牺牲安全性和灵活性为代价的。
  • 限制:vfork()的使用更加受限,因为它要求子进程在调用exec()或exit()之前不能修改任何共享的数据。

在现代的Linux系统和编程实践中,vfork() 的使用已经相当罕见,因为 fork() 加exec()的组合已经足够高效,并且更安全、更灵活。

三、进程的退出 

 进程的正常退出

1、在main函数中执行 return 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函数
3、调用_Exit/_exit函数
        void _exit(int status);
        功能:结束进程,由系统提供的
        void _Exit(int status);
        功能:结束进程,由标准库提供的
        1、它们的参数会被父进程获取到
        2、进程结束前会关闭所有处于打开状态的文件描述符
        3、向父进程发送信号SIGCHLD
        4、该函数也不会返回
4、进程的最后一个线程执行了return返回语句
5、进程的最后一个线程执行了pthread_exit函数 

进程的异常终止 

1、进程调用了abort函数,产生 SIGABRT(6) 信号
2、进程接收到某些信息,可以是其它进程发送的,也可能自己的错误导致的
3、进程的最后一个线程接收到 "取消" 请求操作,并响应

这三种方式结束进程,它的父进程都无法获取结束状态码,因此叫做异常终止
注意:无论进程是如何结束的,它们最后都会执行同一段代码,会关闭所有打开的文件,并释放所有的内存 

四、监控子进程 

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

pid_t waitpid(pid_t pid, int *status, int options);
功能:等待回收指定的某个或某些进程
返回值:
    成功时:返回清理掉的子进程ID。
	失败时:返回-1,并设置errno以指示错误。
	特殊返回值: 如果options中包含了WNOHANG,且没有子进程结束,则返回0。
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函数;在调用函数时,先使用fork创建子进程,再在创建的进程中使用vfork创建孙子进程运行可执行文件。
pid_t wait(int *status);
功能:等待子进程结束,并获取结束状态码
status:输出型参数,接收结束状态码
返回值:结束的子进程的ID
    1、如果所有子进程都还在运行,则阻塞
    2、如果有一个子进程结束,立即返回该进程的结束状态码和ID
    3、如果没有子进程返回-1

WIFEXITED(status) // 判断进程是否是正常结束,如果是返回真
WEXITSTATUS(status) // 如果进程是正常结束的,可以获取到正确的结束状态码
WIFSIGNALED(status) // 判断进程是否异常结束,如果是返回真
WTERMSIG(status) // 如果进程是异常结束的,可以获取到杀死进程的信号
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值