Linux系统编程(进程基础知识讲解)

简介: Linux系统编程(进程基础知识讲解)

前言

本篇文章来讲解Linux中的进程,进程在Linux中是非常重要的一个知识点,掌握好进程是非常重要的。


一、进程的概念

在计算机科学中,进程(Process)是操作系统对正在运行的程序的一种抽象概念。进程可以被看作是一个正在执行的程序的实例。

每个进程都有自己的内存空间(包括代码、数据和堆栈等),它们独立地运行,并且相互之间不会干扰或访问彼此的内存。进程是操作系统进行资源分配和管理的基本单位,它拥有自己的一组系统资源,如处理器时间、内存、文件描述符、打开的文件等。

进程的主要特征包括:

独立性:每个进程都是相互独立的,它们运行在自己的内存空间中,相互之间不干扰,互不影响。

资源拥有:每个进程拥有一组资源,包括处理器时间、内存、文件描述符等。操作系统负责为进程分配和管理这些资源。

执行状态:进程可以处于运行、等待、就绪或终止等不同的执行状态。运行状态表示进程正在执行,等待状态表示进程暂时无法继续执行,就绪状态表示进程已准备好运行但尚未被调度,终止状态表示进程已经完成任务或异常终止。

进程间通信(IPC):进程可以通过各种方式进行通信和协作,如共享内存、文件、管道、消息队列、套接字等方法,实现数据交换和同步。

操作系统通过进程调度算法来管理和调度进程,以便公平地分配系统资源,并确保系统的稳定性和高效性。进程的创建、销毁、通信和同步是计算机系统中重要的组成部分,它们为应用程序提供了一个多任务执行的环境,并支持并发性和多用户操作。


二、进程的生命周期

在Linux中,进程的生命周期包括四个主要阶段:创建(Creation)、运行(Running)、等待(Waiting)和退出(Termination)。下面是对每个阶段的详细描述:

1.创建(Creation): 进程的创建是通过调用 fork 系统调用来实现的。fork 调用会创建一个新的进程(子进程),该子进程是原始进程(父进程)的副本。在创建过程中,子进程获得了父进程的代码、数据和打开的文件描述符等信息。

2.运行(Running): 进程在创建后进入运行阶段。在这个阶段,进程处于活动状态,执行其指定的任务。进程可以占用 CPU 资源并执行相应的操作。进程可能会在执行过程中被抢占或者主动放弃 CPU 资源,以便让其他进程执行。进程可以在用户空间或内核空间运行。

3.等待(Waiting): 进程可以由于某些条件不满足而暂时进入等待状态。在等待状态下,进程停止执行,并将 CPU 资源让给其他进程。进程可以等待多个事件,如等待信号、等待 I/O 完成等。当等待事件发生时,进程将被唤醒并重新进入运行状态。

4.退出(Termination): 进程在完成任务、显式终止或出现异常情况时会退出。进程可以通过调用 exit 系统调用来正常终止,并返回一个退出状态码给父进程。进程的退出时,它的资源(如内存、文件描述符)会被释放,同时系统记录进程的退出状态。

此外,进程的生命周期还受到其他因素的影响,如进程间通信(IPC)和信号处理等。进程可以与其他进程进行通信,共享资源,并根据特定事件或信号做出相应的处理。

需要注意的是,进程的生命周期可能因为特定需求或操作系统策略而有所变化。例如,守护进程(daemon process)通常会在后台长时间运行,而临时进程(ephemeral process)可能只在短暂的时间内存在。此外,进程的创建、运行、等待和退出可以在多个线程执行的上下文中发生,从而创建出多线程的进程。


三、进程树

进程树(Process Tree)是指在一个操作系统中,进程之间的层级关系形成的树状结构。在进程树中,每个进程都有一个父进程,除了根节点(通常是操作系统内核进程或init进程)的父进程为null,其他进程都有且只有一个父进程。而每个进程也可以有多个子进程。

进程树的形成是由于进程的创建和终止操作。当一个进程创建另一个子进程时,子进程成为新创建进程的子节点,而原始进程成为其父进程。这种方式创建了进程的层级结构,以及父子关系。当一个进程终止时,其子进程也可能会随之终止,进一步影响整个进程树。

进程树的一些重要特点和用途包括:

1.层级结构:进程树形成一个明确的层级结构,可以通过查找父进程和子进程来确定进程之间的关联。

2.进程组织:进程树可以将进程有序地组织起来,使操作系统能够有效地管理和跟踪进程的状态和资源。

3.资源分配:进程树中的父进程可以向其子进程分配资源,如文件描述符、内存等。

4.进程终止:当一个父进程终止时,它的子进程可能会成为孤儿进程,被操作系统接管,并由init进程接收,保证整个系统的稳定性。

5.进程通信:进程树中的父子关系可以方便地进行进程间的通信和协作,使用IPC机制进行数据交换和同步。

进程树的可视化表示通常以树状图的形式展现,根节点代表操作系统的内核进程或init进程,其下方的节点表示其他进程,子进程通过连接线与父进程相连接。

总结来说,进程树提供了一个有层级结构的视图,展示了操作系统中进程之间的父子关系。它是理解进程之间相互关联、资源分配和协作的重要工具。

使用ps -ax命令可以查看到进程树:

在Linux系统中,1号进程通常称为init进程。init进程是所有其他进程的祖先进程,是整个进程树的根节点。它是在系统引导时由内核创建的,负责系统初始化和启动用户空间的服务和进程。

init进程的主要任务是完成系统的初始化,并启动其他用户空间的进程和服务。在系统引导过程中,内核会首先加载并运行init进程。init进程会执行系统的初始化脚本和配置文件,设置系统环境,并启动其他一些关键的进程和服务。它是从内核态切换到用户态的第一个进程。

当init进程启动并完成初始化后,它会进入用户态运行。用户态是指进程在操作系统中以普通用户的权限进行操作和执行任务。在用户态下,进程可以访问用户的资源,执行用户的指令,并与其他用户空间进程进行通信。

四、进程的创建

进程创建的具体步骤:

1.为子进程申请内存空间,并将父进程数据完全复制到子进程空间中。

2.两个进程中的程序执行位置完全一致。

调用fork()函数后父进程返回子进程PID,子进程返回0。

代码示例:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
    int pid = 0;
    if((pid = fork() != 0))
    {
        printf("praent pid :%d\n", getpid());
    }
    else
    {
        printf("praent pid :%d\n", getppid());
        printf("current pid = %d\n", getpid());
    } 
    return 0;
}

五、一个进程可以执行几个程序?

一个进程其实是可以执行一个或者多个程序的。

在Unix和类Unix系统中,通过调用execve或其他exec系列函数,一个进程可以加载并执行一个新的程序映像,将其替换当前进程的代码和数据,从而实现在同一个进程内执行多个应用程序的效果。execve函数接受一个新的程序文件路径作为参数,将该文件加载到当前进程的地址空间,并且开始执行新的程序。

当进程调用execve函数时,它的地址空间和资源将被新程序替换,包括代码段、数据段、堆栈、文件描述符等。这样,进程会开始执行新程序的入口点,原来的程序会停止执行。

需要注意的是,execve函数本身并不是创建新进程的函数,它是在已有的进程中执行一个新程序。通过在fork创建的子进程中调用execve函数,可以实现替换子进程原有程序的效果。这样,一个进程可以先通过fork创建新的子进程,然后在子进程中使用execve函数执行其他程序。

因此,通过调用execve或其他exec系列函数,一个进程可以在同一个进程内执行多个应用程序,每次调用execve都会替换当前进程的代码和数据,从而实现多个应用程序的执行。

注意:execve系统调用并不会创建新的进程

execve函数原型:

int execve(const char *filename, char *const argv[], char *const envp[]);

参数说明:

filename:要执行的程序文件的路径。

argv:一个空指针结尾的字符串数组,用于传递给执行的程序的命令行参数。数组的第一个字符串通常是程序的名称。

envp:一个空指针结尾的字符串数组,用于传递给执行的程序的环境变量。数组中的每个字符串都是一个形如"key=value"的环境变量设置。

返回值:

成功执行时,execve函数不会返回,因为它会将当前进程替换为新的程序映像。如果发生错误,execve函数返回-1,并设置errno变量来指示具体的错误信息。

execve函数的执行过程如下:

1.execve函数加载指定的程序文件(filename)到当前进程的地址空间。这通常涉及到读取可执行文件的内容到内存中,建立新的程序的代码段、数据段等内存映射。

2.execve函数设置新程序的入口地址,并将参数(argv和envp)传递给新程序,使得新程序可以访问这些参数。

3.execve函数开始执行新的程序,并取代当前进程的执行,新程序开始从入口地址开始执行。

fork.c:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#define EXE "test"
int main(void)
{
    int pid = 0;
    char* argv[2] = {EXE, NULL};
    printf("begin\n");
    printf("now pid : %d\n", getpid());
    execve(EXE, argv, NULL);
    printf("end\n");
    return 0;
}

test.c

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
    printf("Hello World current pid :%d\n", getpid());
    return 0;
}

这里使用execve函数来加载test这个可执行程序:

执行结果:

从结果上来看execve函数并不会创建出一个新的进程。

六、子进程中调用execve函数

execve函数常用于在子进程中启动新的程序,例如在使用fork函数创建子进程后,子进程通过调用execve函数来执行其他程序,实现进程的程序替换。这是实现进程间通信和程序的动态加载的重要手段之一。

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#define EXE "test"
int main(void)
{
    int pid = 0;
    char* argv[2] = {EXE, NULL};
    printf("begin\n");
    printf("now pid : %d\n", getpid());
    if((pid = fork()) != 0)
    {
        //父进程
    }
    else
    {
        //子进程
        execve(EXE, argv, NULL);
    }
    printf("end\n");
    return 0;
}

执行结果:

总结

本篇文章就讲解到这里,下篇文章继续讲解进程的知识。

相关文章
|
17天前
|
算法 Linux 调度
深入理解Linux操作系统的进程管理
本文旨在探讨Linux操作系统中的进程管理机制,包括进程的创建、执行、调度和终止等环节。通过对Linux内核中相关模块的分析,揭示其高效的进程管理策略,为开发者提供优化程序性能和资源利用率的参考。
43 1
|
6天前
|
存储 缓存 监控
Linux缓存管理:如何安全地清理系统缓存
在Linux系统中,内存管理至关重要。本文详细介绍了如何安全地清理系统缓存,特别是通过使用`/proc/sys/vm/drop_caches`接口。内容包括清理缓存的原因、步骤、注意事项和最佳实践,帮助你在必要时优化系统性能。
118 78
|
9天前
|
Linux Shell 网络安全
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
本指南介绍如何利用 HTA 文件和 Metasploit 框架进行渗透测试。通过创建反向 shell、生成 HTA 文件、设置 HTTP 服务器和发送文件,最终实现对目标系统的控制。适用于教育目的,需合法授权。
42 9
Kali Linux系统Metasploit框架利用 HTA 文件进行渗透测试实验
|
5天前
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
49 13
|
12天前
|
SQL 运维 监控
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
南大通用GBase 8a MPP Cluster Linux端SQL进程监控工具
|
20天前
|
运维 监控 Linux
Linux操作系统的守护进程与服务管理深度剖析####
本文作为一篇技术性文章,旨在深入探讨Linux操作系统中守护进程与服务管理的机制、工具及实践策略。不同于传统的摘要概述,本文将以“守护进程的生命周期”为核心线索,串联起Linux服务管理的各个方面,从守护进程的定义与特性出发,逐步深入到Systemd的工作原理、服务单元文件编写、服务状态管理以及故障排查技巧,为读者呈现一幅Linux服务管理的全景图。 ####
|
25天前
|
缓存 算法 Linux
Linux内核的心脏:深入理解进程调度器
本文探讨了Linux操作系统中至关重要的组成部分——进程调度器。通过分析其工作原理、调度算法以及在不同场景下的表现,揭示它是如何高效管理CPU资源,确保系统响应性和公平性的。本文旨在为读者提供一个清晰的视图,了解在多任务环境下,Linux是如何智能地分配处理器时间给各个进程的。
|
6天前
|
Ubuntu Linux C++
Win10系统上直接使用linux子系统教程(仅需五步!超简单,快速上手)
本文介绍了如何在Windows 10上安装并使用Linux子系统。首先,通过应用商店安装Windows Terminal和Linux系统(如Ubuntu)。接着,在控制面板中启用“适用于Linux的Windows子系统”并重启电脑。最后,在Windows Terminal中选择安装的Linux系统即可开始使用。文中还提供了注意事项和进一步配置的链接。
24 0
|
17天前
|
存储 Oracle 安全
服务器数据恢复—LINUX系统删除/格式化的数据恢复流程
Linux操作系统是世界上流行的操作系统之一,被广泛用于服务器、个人电脑、移动设备和嵌入式系统。Linux系统下数据被误删除或者误格式化的问题非常普遍。下面北亚企安数据恢复工程师简单聊一下基于linux的文件系统(EXT2/EXT3/EXT4/Reiserfs/Xfs) 下删除或者格式化的数据恢复流程和可行性。
|
5月前
|
运维 关系型数据库 MySQL
掌握taskset:优化你的Linux进程,提升系统性能
在多核处理器成为现代计算标准的今天,运维人员和性能调优人员面临着如何有效利用这些处理能力的挑战。优化进程运行的位置不仅可以提高性能,还能更好地管理和分配系统资源。 其中,taskset命令是一个强大的工具,它允许管理员将进程绑定到特定的CPU核心,减少上下文切换的开销,从而提升整体效率。
掌握taskset:优化你的Linux进程,提升系统性能