《linux系统编程 —— 1.进程的概念》

1.问题的引入

        顺序执行无法执行不同的任务,例如无法在触摸屏阻塞等待时显示系统时间。这就需要并发来执行不同的任务。又例如在动态壁纸显示时,触摸函数依旧会阻塞动态壁纸的切换显示。此外,我们可能需要通过一个程序去控制另外一个程序,这就需要实现不同程序之间的通信。

2.解决方案

        因此,我们需要引入系统编程(并发编程)去解决这些问题。用于实现多个任务和实现不同程序之间的数据交流和协作。

2.1 并发执行

        指在同一时间段内同时执行多个任务或操作。在复杂项目中,有时需要同时进行多个任务,以提高系统的效率和性能。这些任务可以是独立的,也可以是彼此依赖的。通过并发执行,可以充分利用计算机的资源,提高系统的吞吐量和响应能力。

        吞吐量(Throughput):吞吐量指的是在单位时间内完成的任务或处理的数据量。它表示系统在一定时间内能够处理的工作量。通常以每秒钟完成的任务数量或每秒钟处理的数据量来衡量。高吞吐量表示系统能够高效地处理大量任务或数据,具有较好的性能。

2.1.1 提高系统的性能

        通过同时执行多个任务,可以充分利用计算机的处理能力,提高系统的吞吐量和响应能力,缩短任务的执行时间。

2.1.2 实现任务的并行化

        有些任务可以被分解为多个子任务,并行执行这些子任务可以加快整个任务的完成时间。

2.1.3 解耦和模块化

        1. 解耦性(Decoupling):并发编程可以将不同的模块或任务解耦,使它们可以独立地进行并发执行,而不需要相互依赖或串行执行。通过并发执行,模块之间的通信和数据交换可以通过消息传递、共享内存等机制进行,而不需要直接的函数调用或依赖关系。这种解耦性使得系统的各个模能够独立开发、测试和维护,提高了代码的可重用性和可维护性。

        2. 模块化(Modularity):并发编程可以帮助将复杂的系统划分为多个独立的模块或任务,每个模块负责一个特定的功能或子任务。这些模块可以并发地执行,相互之间通过消息传递或共享的数据进行通信。通过模块化的设计,系统的不同功能可以被独立地开发和测试,从而降低了系统的复杂性和耦合度。此外,如果需要增加新的功能或模块,可以相对容易地扩展系统,而无需修改已有的模块。

        并发编程可以提供更灵活和模块化的系统设计,使得系统更易于维护、扩展和重用。通过解耦和模块化,不同的模块可以并发地执行,提高系统的效率和性能。同时,它也使得系统的各个模块之间的依赖性降低,减少了代码的耦合度,使得系统更具可靠性和可扩展性。

2.2 程序间通信

        在多任务或多进程的系统中,程序间通信(Inter-Process Communication,IPC)是指不同的程序之间进行交流、传递数据或共享资源的机制。不同程序之间进行数据交换和协作的过程。在复杂项目中,不同的程序可能需要共享数据、协调操作或进行信息传递。程序间通信可以帮助不同程序之间实现数据共享、任务协作和系统集成。

2.2.1 数据共享

        不同程序可能需要共享数据,以便彼此之间进行交互和共同处理。通过程序间通信,可以实现数据的传递和共享,使得不同程序之间可以访问和操作共享的数据。

2.2.2 任务协作

        任务协作是指通过IPC通信机制,使不同的程序能够协同工作、共同完成一个任务或解决一个问题。

        常见的任务协作模式和 IPC 机制包括:

        1. 进程间通信(Inter-Process Communication):通过消息传递、共享内存、管道、套接字等方式,在不同的进程之间传递数据和通信。

        2. 线程间通信(Inter-Thread Communication):在同一进程中的不同线程之间进行通信,可以使用共享内存、互斥锁、条件变量、消息队列等方式。

        3. 客户端-服务器模式:客户端程序和服务器程序之间通过网络进行通信,客户端发送请求,服务器进行响应和处理。

        4. 同步和互斥机制:通过锁、信号量、条件变量等同步和互斥的机制,确保多个程序或线程按照特定的顺序执行,避免竞态条件和资源冲突。

2.2.3 系统集成

        复杂项目通常由多个程序组成,这些程序可能来自不同的团队或部门。通过程序间通信,可以实现不同程序之间的集成,使得它们可以相互协作、共享资源和实现系统级功能。

2.2.4 总结

        在实现程序间通信时,可以使用多种通信机制,如进程间通信(IPC)、套接字(Socket)、消息队列、共享内存等。这些通信机制提供了不同的方式来实现程序间的数据交换和协作,可以根据具体的需求选择合适的通信方式。

3.程序与进程

3.1 什么是程序

        1、程序是一组指令的集合,描述了完成特定任务的步骤和逻辑。

        2、程序是静态的,通常以文本文件的形式存在,包含了编程语言的代码。

        3、程序是开发人员编写的,用于告诉计算机执行特定的任务。

        4、程序可以被编写成可执行文件,或者在解释器中逐行执行。

        5、程序本身不占用计算机资源,只有被加载到内存并由操作系统调度执行才变成活动状态。

        程序什么时候被加载到内存变成活动状态?

        运行一个程序的时候,本质就是由操作系统为这个程序创建了一个进程,分配了其所需的资源,并将其放入调度队列。

        调度队列:调度队列是操作系统中用于管理和调度进程或线程的数据结构。它是一个存储待执行任务的队列,其中包含了系统中所有就绪(可执行)的进程或线程。调度队列的目的是根据特定的调度算法,决定下一个要执行的任务。调度队列通常是按照优先级、先来先服务(FCFS)、时间片轮转等调度策略来组织的。不同的调度算法会决定进程或线程在调度队列中的排列顺序,从而影响其执行顺序。

3.2 什么是进程

1、进程是程序的一次执行过程,是操作系统进行资源分配和调度的基本单位。

2、进程是一个活动的实体,具有独立的执行状态和控制流程

3、每个进程都有自己独立的内存空间,包括代码、数据、堆栈和寄存器等执行的所需信息。

4、进程可以被操作系统调度和终止。

5、进程之间相互独立,除非使用特定的调度机制。

         在Linux中,程序文件的格式都是 ELF,这些文件在被执行的瞬间,就被载入内存,所谓的载入内存,如上图所示,就是将数据段、代码段这些运行时必要的资源拷贝到内存,另外系统会再分配相应的栈、堆等内存空间给这个进程,使之成为一个动态的实体。

3.3 如何得知程序与进程

程序: elf 文件格式

进程:ps -ef        //只能查看静态的

3.4 区别

3.4.1 静态与动态

程序:静态、是一组指令的集合,描述任务逻辑   

进程:动态、具有独立的执行状态和资源分配    

3.4.2 存储方式

程序:文本形式存在,包含了编写语言的代码       

进程:内存空间存在,包含了数据段、代码段堆栈和寄存器等

3.4.3 资源分配

程序:不占用CPU资源       

进程:由操作系统创建并分配资源,如内存、CPU时间

3.4.4 并发执行

程序:无法并发执行           

进程:可以并发执行

3.4.5 内存隔离

程序:相互独立                  

进程:相互隔离

3.4.6 通信和协作

程序之间可以通过程序间通信机制进行数据交换和协作。

进程之间也可以通过进程间通信机制进行数据交换和协作,实现任务的分配、同步和系统集成。

4.进程的组织形式

4.1 单进程

系统中只有一个执行单元的进程。在单进程的组织形式下,所有任务和操作都在一个进程执行。

例如一个简单的计算器就只有一个进程。

4.2 多进程

        多进程是指系统中有多个独立的执行单元的进程。每个进程都有自己的地址空间和资源,它们可以并发执行不同的任务。多进程的组织形式可以通过创建多个独立的进程来实现并发执行和多任务处理。不同进程之间可以通过进程间通信(IPC)机制进行数据交换和协作。

        例如:一个 Web 服务器,每个客户端请求都由一个独立的进程来处理,这样可以同时处理多个请求。

        例如:图像处理软件,将图像分成多个区块,每个区块由一个独立的进程处理,最后将结果合并。

4.3 线程

        线程是进程中的一个执行单元,一个进程可以包含多个线程。线程共享进程的地址空间和资源,可以并发执行不同的任务。线程之间的切换开销较小,适合于实现轻量级的并发执行和任务处理。线程之间可以通过共享内存来进行数据交换,也可以通过线程间通信(如消息队列、信号量等)来实现协作和同步。

        例如:一个多线程的网络下载器,每个线程负责下载文件的一部分,从而加快下载速度。
        例如:图形用户界面(GUI)应用程序,使用一个线程处理用户界面的响应,另一个线程处理后台的计算任务。

4.4 线程池

        线程池是一种预先创建一组线程,并将任务分配给这些线程执行的机制。线程池中的线程可以重复使用,避免了频繁创建和销毁线程的开销。线程池可以提高任务的执行效率和响应速度,适用于需要大量并发执行任务的场景。

        例如:一个 Web 服务器使用线程池来处理客户端请求,避免了为每个请求创建和销毁线程的开销。

        例如:数据库连接池,预先创建一组线程用于处理数据库请求,提高数据库操作的效率。

4.5 分布式进程

        分布式进程是指将任务分布到多台计算机上执行的一种组织形式。每台计算机都运行着独立的进程,它们通过网络进行通信和协作。分布式进程可以提高系统的可扩展性和容错性,适用于处理大规模任务和构建高可用系统的场景。

        1、一个大规模数据处理系统,将数据分发到多台计算机上的独立进程进行并行处理,加快处理速度。

        2、一个分布式存储系统,将数据分布在多台计算机上的进程中,提高系统的容错性和可扩展性。

        这些进程的组织形式可以根据具体的应用场景和需求进行选择和组合。例如,在复杂项目中可能同时使用多进程和线程来实现并发执行和任务处理,同时结合进程间通信和线程间通信来实现程序间的数据交换和协作。

4.6 进程树

        在 Linux 系统中,除了系统的初始进程之外,其余所有进程都是通过从一个父进程( parent )复刻( fork )而来的,有点像人类社会,每个个体都是由亲代父母繁衍而来。

        因此,在整个 Linux 系统中,所有的进程都起源于相同的初始进程,它们之间形成一棵倒置的进程树,就像家族族谱,可以使用命令 pstree 查看这些进程的关系。

5.进程的复刻

        在 Linux 系统中,除了系统的初始进程之外,其余所有进程都是通过从一个父进程( parent )复刻( fork )而来的。当一个进程复制(fork)出一个子进程时,子进程会复制父进程的某些资源。下面是进程复制时会涉及的资源:

        1. 进程上下文(Process Context):子进程会继承父进程的进程上下文,包括程序计数器(Program Counter)、寄存器的值、栈指针(Stack Pointer)、进程状态等。

        2. 内存映像(Memory Image):子进程会复制父进程的内存映像,包括代码段、数据段和堆段。

        3. 文件描述符表(File Descriptor Table):子进程会复制父进程的文件描述符表,其中包括打开的文件描述符和相应的文件指针。子进程和父进程共享相同的文件打开状态,但是它们有独立的文件描述符副本。也就是说会复制父进程打开的文件。

        4. 信号处理器(Signal Handlers):子进程会继承父进程的信号处理器。也就是说,子进程会继承父进程为各个信号所设置的处理函数。即形如signal(SIGINT, signal_handler);的信号响应函数。

        5. 进程ID(Process ID):子进程会获得一个新的进程ID。子进程的进程ID不同于父进程的进程ID,但是它会继承父进程的进程组ID和会话ID。也就是实际UID和GID

        6. 进程资源限制(Process Resource Limits):子进程会继承父进程的进程资源限制,例如最大文件打开数和CPU时间限制等。

        除了上述资源会被复制,还有一些资源不会被复制给子进程:

        1.进程号PID。PID是身份证号码,哪怕亲如父子,也要区分开。
        2.记录锁。父进程对某文件加了把锁,子进程不会继承这把锁。

        3. 进程间通信(Interprocess Communication):子进程不会直接继承父进程的通信机制,如管道、消息队列、挂起的信号、共享内存等。但是,子进程可以通过文件描述符的继承来访问这些通信机制。

        4. 计时器(Timers):子进程不会继承父进程设置的计时器。

        总结:  

        尽管子进程继承了许多父进程的资源,但是子进程是一个独立的进程,它有自己的地址空间和执行环境。子进程可以根据需要修改或扩展继承的资源,而不会影响到父进程和其他子进程。

6. 进程的状态

        进程是动态的活动的实体,因此会有很多种运行状态:一会儿睡眠、一会儿暂停、一会儿又继续执行。下图给出Linux进程从被创建(生)到被回收(死)的全部状态,以及这些状态发生转换时的条件:

        在一些常见的进程状态中,常常会看到一些额外的状态标识。通常可以分为就绪态、睡眠态、挂起态、暂停态、执行态、僵尸态和死亡态:

  1. 就绪态(Ready):表示进程已经准备好执行,等待被调度执行。就像在计算机中,多个进程都处于就绪态,等待处理器分配时间片执行。在现实生活中,可以将就绪态比作等待在红绿灯前的车辆,它们已经准备好行驶,只需要等待信号灯变绿。

  2. 睡眠态(Sleep):当进程需要等待某个事件发生时,它可能会进入睡眠态。在这个状态下,进程暂时停止执行,直到等待的事件发生。在计算机中,一个进程可能等待用户输入或等待某个资源可用。在现实生活中,可以将睡眠态类比为一个人在等待朋友的电话,他暂时停止了其他活动,直到电话响起。

  3. 挂起态(Suspended):当一个进程被暂时移出内存,以释放资源时,它进入挂起态。在这个状态下,进程的状态被保存到磁盘上,等待重新唤醒。在计算机中,操作系统可以将某些不常用的进程挂起,以便为其他进程腾出更多资源。在现实生活中,可以将挂起态类比为一个人离开了某个地方,他的状态被记录下来,以便日后再次回来。

  4. 暂停态(Paused):当一个进程被手动暂停时,它进入暂停态。在这个状态下,进程停止执行,直到被手动恢复。在计算机中,可以通过调试工具暂停进程的执行,以便检查程序的状态和变量。在现实生活中,可以将暂停态比作一个人在玩游戏时按下了暂停按钮,游戏暂时停止,直到按下继续按钮。

  5. 执行态(Running):当一个进程正在被处理器执行时,它处于执行态。在这个状态下,进程正在进行计算和操作。在计算机中,多个进程会轮流获得处理器的时间片执行任务。在现实生活中,可以将执行态比作一个人在进行某项任务或活动。

  6. 僵尸态(Zombie):当一个进程已经终止,但其相关信息(如退出状态)还未被父进程获取时,它进入僵尸态。在这个状态下,进程的资源已被释放,但它的存在仍被保留,以便父进程处理。在计算机中,僵尸进程是一种已经终止但仍存在于进程表中的进程。在现实生活中,可以将僵尸态比作一个人已经离开了,但他的离开消息还未传达给其他人。

  7. 死亡态(Terminated):当一个进程完成了它的任务或被手动终止时,它进入死亡态。在这个状态下,进程的生命周期结束,它的资源被释放。在计算机中,进程在完成任务后会进入死亡态。在现实生活中,可以将死亡态比作一个人完成了某项任务或活动,或者离开了某个地方。

        这些状态是抽象的概念,用来描述进程在操作系统中的状态变化。实际的进程管理和状态转换由操作系统内核进行控制和管理。 这些状态标识可能会根据不同的操作系统或不同的工具而有所不同。例如,在Linux系统中,`ps` 命令的输出通常会包含下面这些状态标识,用于描述进程的当前状态。

文档内容解释如下

- D:不可中断的睡眠(通常是等待IO完成)。进程正在执行一个无法被中断的睡眠操作,通常是等待IO操作完成,例如磁盘读写。
- R:运行或可运行(在运行队列中等待调度)。进程正在运行或准备好运行,处于可被调度的状态,可能正在使用处理器执行指令。
- S:可中断的睡眠(等待某个事件完成)。进程正在等待某个事件的发生,并且可以通过信号中断。
- T:被作业控制信号停止。进程被作业控制信号(例如Ctrl+Z)停止执行,一般是由于用户的交互操作。
- t:在跟踪期间被调试器停止。进程在被调试器追踪期间被暂停执行。
- W:页面换出(在2.6.xx内核之后不再使用)。该状态已经不再有效,自Linux内核2.6版本之后不再使用。
- X:已经终止(不应该出现)。该状态表示进程已经终止,但是正常情况下不应该出现在进程列表中。
- Z:僵尸进程,已经终止但未被父进程回收。进程已经终止,但是其相关的进程表项还未被其父进程回收,称为僵尸进程。

对于BSD格式和在使用 `stat` 关键字时,可能会显示额外的字符:

- \<:高优先级(对其他用户不友好)。
- N:低优先级(对其他用户友好)。
- L:已将页面锁定在内存中(用于实时和自定义IO)。
- s:会话(session)领导者。
- l:多线程(使用CLONE_THREAD,类似于NPTL pthreads)。
- +:处于前台进程组中。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值