进程单元测试题

一、选择题(每题2分,总分30分)

1. 下列不是用户进程的组成部分的是( D )

[A] 正文段        [B] 用户数据段        [C] 系统数据段        [D]  elf段

根据进程的基本概念,进程是由正文段、用户数据段以及系统数据段共同组成的一个执行环境。其中:

  • 正文段(也称为代码段):存放被执行的机器指令,这部分是只读的,允许正在运行的两个或多个进程之间共享这一代码。
  • 用户数据段:存放进程在执行时直接进行操作的所有数据,包括进程使用的全部变量在内,这部分的信息可以被改变。每个进程需要有自己的专用用户数据段。
  • 系统数据段:有效地存放程序运行的环境,包括进程的控制信息等。

选项D中的"elf段"。ELF(Executable and Linkable Format)是一种用于可执行文件、目标代码、共享库以及核心转储的标准文件格式。虽然ELF文件与进程的执行有关,但"elf段"本身并不是用户进程的组成部分。

【正确答案:[D]  elf段】

2.  以下哪种不是进程的类型 (  )

   [A]  批处理进程       [B] 管理进程     [C] 交互进程      [D]  守护进程

回顾一下进程的类型:

  • [A] 批处理进程:这是与Windows原来的批处理类似的进程,是一个进程序列,负责按照顺序启动其他进程。
  • [C] 交互进程:这是由shell启动的进程,既可以在前台运行,也可以在后台运行。在执行过程中,它要求与用户进行交互操作。
  • [D] 守护进程:这是执行特定功能或执行系统相关任务的后台进程。它不是内核的组成部分,但在系统启动时可能启动,并在系统关闭前持续运行。

选项B:管理进程。

在操作系统的上下文中,虽然有一个进程管理系统(负责创建、调度、终止进程等),但“管理进程”本身并不是一个特定的进程类型。它是一个更广泛的概念,涉及多个进程和组件,用于管理系统的各种资源。

【正确答案:[B] 管理进程】

3. 如果umask的值为022,创建文件时指定的权限是775,则该文件的实际权限为 (  )

   [A] 755        [B] 722       [C] 753       [D] 022

在Unix和类Unix系统中,umask是一个用于控制新创建文件和目录权限的遮罩值。当创建一个新文件或目录时,系统会使用你指定的权限(如775对于文件或777对于目录)与umask值进行按位与的反操作(即AND NOT~AND)来确定最终的权限。

首先,解释给定的权限和umask值:

  • 文件指定的权限:775(二进制为111 111 101
  • umask的值:022(二进制为000 010 010

使用按位与的反操作来确定最终权限:

  • 对于用户(owner)权限:111 AND NOT 000 = 111(即7)
  • 对于组(group)权限:111 AND NOT 010 = 101(即5)
  • 对于其他用户(others)权限:101 AND NOT 010 = 101(即5)

【正确答案: [A] 755】

4. 用open( )创建新文件时,若该文件存在则可以返回错误信息的参数是 (  )

[A] O_CREAT     [B] O_EXCL      [C] O_TRUNC       [D] O_NOCTTY

在Unix和类Unix系统的open()系统调用中,要创建新文件或打开已存在的文件,并希望在文件已存在时返回错误信息,你需要使用特定的标志组合。

  • [A] O_CREAT:如果文件不存在,则创建它。如果文件已存在,则此选项不产生任何效果。
  • [B] O_EXCL:与O_CREAT一起使用时,如果文件已存在,则调用失败并返回错误。
  • [C] O_TRUNC:如果文件存在且为普通文件,并且成功打开以进行写操作或读/写操作,则将其长度截断为零。此选项对打开已存在的文件有影响,但它不会阻止文件被打开。
  • [D] O_NOCTTY:如果路径名指向终端设备,则不要将该设备分配为进程的控制终端。这与文件是否已存在无关。

因此,要在文件已存在时返回错误信息,你需要使用O_CREATO_EXCL这两个标志的组合。但根据题目要求,单独的标志是O_EXCL

【正确答案: [B] O_EXCL】

5. 如果键盘输入为abcdef,程序如下所示,打印结果应该是(  )

   char  buffer[6];

   ……

   fgets(buffer, 6, stdin);

   printf(“%s”, buffer);

   [A]  abcde       [B] abcdef     [C] abcdef 后出现乱码     [D]  段错误

首先,理解fgets函数的工作原理。fgets函数从指定的流(在这个例子中是stdin)中读取一行,并将其存储在提供的缓冲区中,直到遇到换行符('\n')、EOF或达到指定的最大字符数(包括空字符'\0')。

给定的代码片段中,fgets(buffer, 6, stdin);尝试从stdin读取最多5个字符(因为缓冲区大小为6,但还需要一个位置来存储字符串的结束符'\0')并存储在buffer数组中。

现在,键盘输入为abcdef

  1. fgets会读取前5个字符('a', 'b', 'c', 'd', 'e')并存储在buffer中。
  2. fgets会在字符串的末尾添加一个空字符'\0'来标记字符串的结束。
  3. 然后,printf(“%s”, buffer);会打印出存储在buffer中的字符串,即abcde

由于fgets在读取5个字符后遇到换行符('\n')之前就已经达到了最大字符数,所以换行符不会被读取到buffer中,因此输出中也不会包含换行符或任何其他额外的字符(如选项C中的乱码)。

另外,由于缓冲区足够大来存储输入的字符串和结束符,所以也不会出现段错误(如选项D)。

【正确答案: [A]  abcde】

6. 下列哪个函数无法传递进程结束时的状态 (  )

[A]close         [B] exit          [C] _exit       [D] return

首先,理解这些函数在Unix和类Unix系统中的用途和上下文。

  • [A] close:这个函数用于关闭一个文件描述符。它并不涉及进程的结束或状态传递。
  • [B] exit:这个函数用于终止调用它的进程,并返回一个状态值给父进程。父进程可以通过wait()waitpid()等系统调用来获取这个状态值。
  • [C] _exit:这个函数也用于终止进程,但它不会执行标准I/O清理(如刷新缓冲区)。它也会返回一个状态值给父进程。
  • [D] return:在main函数中,return语句用于结束程序并返回一个状态值给操作系统(实际上是传递给父进程)。在非main函数中,return只返回给调用它的函数。

分析这些选项:

  • [A] close 显然与进程结束时的状态传递无关。
  • [B] exit 和 [C] _exit 都可以传递一个状态值给父进程。
  • [D] return 在main函数中也可以传递一个状态值给操作系统(实际上是父进程)。

但是,如果return在非main函数中使用,它就不会传递进程结束时的状态给父进程,因为它只是返回给调用它的函数。但题目没有明确说return是在哪个函数中使用,所以我们假设它是在main函数中使用的。

然而,从题目的语境来看,我们是在寻找一个“无法传递进程结束时的状态”的函数,而这个函数就是[A] close

【正确答案: [A]close】

7. 以下哪种用法可以等待接收进程号为pid的子进程的退出状态 (  )

 [A]  waitpid(pid, &status, 0)         [B]  waitpid(pid, &status, WNOHANG)

  [C]  waitpid(-1, &status, 0)          [D]  waitpid(-1, &status, WNOHANG)

在给出的选项中,要等待接收进程号为pid的子进程的退出状态,应该使用waitpid函数并指定正确的参数。

  • [A] waitpid(pid, &status, 0):这个用法会阻塞父进程,直到进程ID为pid的子进程结束,并将子进程的退出状态保存在status指向的变量中。这是符合题目要求的。
  • [B] waitpid(pid, &status, WNOHANG):这个用法使用WNOHANG选项,它会使waitpid在子进程没有结束时立即返回。这意味着父进程不会被阻塞,但可能无法立即获取子进程的退出状态。
  • [C] waitpid(-1, &status, 0):这个用法会阻塞父进程,直到任意一个子进程结束。因为指定了-1作为进程ID,所以它会等待任何子进程。
  • [D] waitpid(-1, &status, WNOHANG):这个用法与[B]类似,但会等待任意子进程。同样,由于使用了WNOHANG选项,父进程可能不会被阻塞,但也可能无法立即获取子进程的退出状态。

因此,要等待接收进程号为pid的子进程的退出状态,应该使用[A] waitpid(pid, &status, 0)。这个选项会阻塞父进程,直到指定的子进程结束,并获取其退出状态。

【正确答案: [A]  waitpid(pid, &status, 0)】

8. 函数waitpid的返回值等于0时表示的含义是 (  )

   [A] 等待的子进程退出    [B] 使用选项WNOHANG且没有子进程退出

   [C] 调用出错            [D] 不确定

函数waitpid的返回值等于0时,表示的含义是:

[B] 使用选项WNOHANG且没有子进程退出。

waitpid函数被调用时,如果设置了WNOHANG选项(表示非阻塞模式),并且指定的子进程尚未退出,那么waitpid会立即返回,并且返回值是0。这表示没有已退出的子进程可供收集。

分析:

选项[A]表示等待的子进程已经退出,此时waitpid的返回值应该是子进程的进程ID,而不是0。

选项[C]表示调用出错,此时waitpid的返回值是-1。

选项[D]表示不确定,这是不正确的,因为waitpid的返回值有明确的意义。

【正确答案: [B] 使用选项WNOHANG且没有子进程退出】

9. 下列对无名管道描述错误的是 (  )

[A] 半双工的通信模式       

[B] 有固定的读端和写端

[C] 可以使用lseek函数

[D] 只存在于内存中 

对于无名管道的描述,逐一分析选项:

[A] 半双工的通信模式:这是正确的。无名管道是一种半双工的通信方式,数据在同一时刻只能在一个方向上流动。

[B] 有固定的读端和写端:这也是正确的。在无名管道中,fd[0]固定用于读管道,fd[1]固定用于写管道。

[C] 可以使用lseek函数:这是错误的。无名管道是基于管道的顺序流通信,只支持顺序读写操作,无法像文件一样进行随机访问。因此,它无法使用lseek函数进行随机访问。

[D] 只存在于内存中:这是正确的。无名管道不是普通的文件,不属于某个文件系统,其只存在于内存中。

【正确答案: [C] 可以使用lseek函数】

10.下列对于有名管道描述错误的是 (  )

[A] 可以用于互不相关的进程间    

[B] 通过路径名来打开有名管道

[C] 在文件系统中可见

[D] 管道内容保存在磁盘上

对于有名管道(也称为命名管道或FIFO)的描述,分析选项:

[A] 可以用于互不相关的进程间:这是正确的。有名管道可以被不相关的进程用来进行通信,只要它们有对有名管道的访问权限。

[B] 通过路径名来打开有名管道:这也是正确的。有名管道在文件系统中有一个对应的路径名,可以通过open等系统调用来打开这个路径名以进行读写操作。

[C] 在文件系统中可见:这是正确的。有名管道在文件系统中有一个对应的节点,可以通过ls等命令在文件系统中看到。

[D] 管道内容保存在磁盘上:这是错误的。尽管有名管道在文件系统中有一个对应的节点,但其内容与普通的文件不同。有名管道的内容是保存在内核的缓冲区中的,而不是直接保存在磁盘上。有名管道的内容只在管道打开且进程之间进行通信时存在,当所有对有名管道的引用都关闭后,其内容将被丢弃。

【正确答案: [D] 管道内容保存在磁盘上】

11. 下列不属于用户进程对信号的响应方式的是 (  )

 [A] 忽略信号      [B] 保存信号       [C] 捕捉信号       [D] 按缺省方式处理

在Linux系统中,用户进程对信号的响应方式主要有三种

分析:

  • [A] 忽略信号:进程可以选择忽略某些信号,即不对信号做出任何响应。但需要注意的是,有两个信号(SIGKILL和SIGSTOP)是不能被忽略的。
  • [B] 保存信号:这并不是用户进程对信号的响应方式之一。进程在接收到信号后,通常选择忽略、捕捉或按缺省方式处理,但不会直接“保存”信号。
  • [C] 捕捉信号:进程可以定义自己的信号处理函数,当接收到某个信号时,就执行这个处理函数。这种方式允许进程对信号进行自定义的响应。
  • [D] 按缺省方式处理:如果没有特殊指定,Linux系统会对每种信号都规定一个默认的操作。当进程接收到信号时,如果没有其他的处理方式,就会按照这种默认的操作来响应。

【正确答案: [B] 保存信号】

12. 不能被用户进程屏蔽的信号是 (  )

    [A] SIGINT        [B] SIGSTOP        [C] SIGQUIT         [D] SIGILL

在Linux系统中,信号屏蔽是指进程暂时阻塞某些信号的传递和处理。但并非所有的信号都可以被用户进程屏蔽。

  • [A] SIGINT:这是一个中断信号,通常由用户按下Ctrl+C产生。这个信号可以被用户进程屏蔽。
  • [B] SIGSTOP:这是一个停止信号,用于立即停止进程的执行。这个信号是不能被用户进程屏蔽的。
  • [C] SIGQUIT:这是一个退出信号,通常由用户按下Ctrl+\产生。这个信号可以被用户进程屏蔽。
  • [D] SIGILL:这是一个非法指令信号,表示进程执行了非法的指令。这个信号通常与硬件或程序错误相关,但也可以被用户进程屏蔽。

【正确答案: [B] SIGSTOP】

13. 默认情况下,不会终止进程的信号是 (  )

    [A] SIGINT        [B] SIGKILL         [C] SIGALRM        [D]  SIGCHLD

选项解析:

        1. SIGINT: 当用户在终端中按下Ctrl+C时,会产生此信号。它的默认行为是**终止进程**,并生成core dump文件(如果配置允许的话)。这个信号通常用来请求命令或程序的中断。
        2. SIGKILL: 此信号用于立即结束进程,并且它不能被捕获、阻塞或忽略。发送SIGKILL信号到进程是确保该进程会被终止的唯一方法,因此它**会终止进程**。
        3. SIGALRM: 此信号在定时器到期时产生。其默认行为是**终止进程**,但进程可以选择捕获此信号并执行其他操作。
        4. SIGCHLD: 此信号在子进程停止或终止时由操作系统发送给父进程。默认情况下,它**不会终止进程**,而是通知父进程需要检查子进程的状态并进行适当的清理工作,如读取子进程的退出状态。

【正确答案: [D]  SIGCHLD 】

14. 下列不属于IPC对象的是 (  )

[A] 管道        [B] 共享内存       [C] 消息队列         [D]  信号灯

在进程间通信(IPC)的上下文中,我们考虑以下选项:

  • [A] 管道(Pipe):管道是IPC的一种机制,它允许不同进程之间进行数据交换。因此,它是IPC对象。
  • [B] 共享内存(Shared Memory):共享内存也是IPC的一种机制,允许进程共享同一块物理内存区域。因此,它也是IPC对象。
  • [C] 消息队列(Message Queue):消息队列是操作系统提供的一种IPC机制,允许进程通过发送和接收消息来进行通信。所以,它也是IPC对象。
  • [D] 信号灯(Signal Light):在IPC的上下文中,我们通常指的是信号量(Semaphore)而不是“信号灯”。信号量是一种用于控制对共享资源的访问的IPC机制。但“信号灯”并不是标准的IPC对象名称。

【正确答案: [D]  信号灯】

15. 下列哪种机制可以用于线程之间的同步 (  )

    [A]  信号                     [B]  IPC信号灯 

    [C]  POSIX有名信号量         [D]  POSIX无名信号量

POSIX(Portable Operating System Interface for Unix)是一个为类Unix操作系统定义的跨平台应用编程接口(API)标准

在给出的选项中,用于线程之间同步的机制有:

  • [D] POSIX无名信号量:这是一种在多线程和多进程编程中用于同步和互斥的机制。它是POSIX标准中定义的一组函数和数据结构,用于实现线程或进程之间的同步。信号量是一个计数器,用于控制对共享资源的访问。它可以用来解决多个线程或进程之间的竞争条件和互斥访问问题。

对于其他选项:

  • [A] 信号:在操作系统中,信号主要用于进程间的通信,而不是线程间的同步。
  • [B] IPC信号灯:这个描述可能有些模糊,但通常IPC中的“信号灯”指的是信号量(Semaphore),而不是一个独立的同步机制。
  • [C] POSIX有名信号量:虽然有名信号量也可以用于同步,但在这个问题中,它并不是唯一或首选的答案,因为无名信号量在多线程同步中更为常见和方便。

【正确答案: [D]  POSIX无名信号量】

二、判断题(每题1分,总分15分)

1.Linux下进程的模式分为用户态,内核态和系统态 (  )

在Linux系统中,进程的运行模式主要分为用户态(user mode)和内核态(kernel mode)。

  1. 用户态(user mode):当进程执行用户自己的代码时,该进程处于用户态。在用户态下,进程可以直接读取用户程序的数据,但此时CPU访问系统资源的权限是受限的。
  2. 内核态(kernel mode):当进程执行系统内核代码时,该进程处于内核态。在内核态下,进程或程序几乎可以访问计算机的任何资源,不受限制。此时,CPU可以访问计算机的所有资源。

【正确答案:×】

2.每个进程的进程号和父进程号在进程执行期间不会改变 (  )

在Linux系统中,每个进程都有一个唯一的进程ID(PID)和一个父进程ID(PPID)。这些ID在进程创建时由内核分配,并且在进程的生命周期内保持不变。即使进程执行了exec系统调用(这会导致进程加载并执行新的程序),其PID和PPID也不会改变。只有当进程结束时,它的PID才会被释放,以便后续创建的进程可以使用。

【正确答案:

3.子进程被创建后从fork()的下一条语句开始执行 (  )

在Linux系统中,当调用fork()函数时,会创建一个与父进程几乎完全相同的子进程。这个“几乎完全相同”的意思包括子进程会获得父进程当前执行点的复制品,也就是说,子进程会从fork()系统调用的下一条语句开始执行。

同时,父进程会继续执行fork()之后的语句,而子进程则执行它自己的副本中的相同代码部分。这样,父子进程就有了各自独立的执行路径,但它们都起源于fork()调用。

【正确答案:

4.子进程的进程号等于父进程的进程号加1 (  )

这个说法是不正确的。在Linux系统中,子进程的进程ID(PID)是由系统内核在创建子进程时分配的,它并不简单地等于父进程的PID加1。实际上,PID是一个唯一的、非负的整数,用于标识系统中的每个进程。

当父进程使用fork()系统调用创建子进程时,内核会为新进程分配一个新的、唯一的PID。这个PID的分配过程通常是由操作系统内核的调度器来管理的,而不是简单地基于父进程的PID来生成。

【正确答案:×】

5.执行_exit()函数时不会清理IO缓冲 (  )

        _exit():这个函数是一个系统调用,它直接将控制权返回给内核,而不进行任何额外的清理工作。这意味着不会执行标准I/O缓冲区的刷新、不会关闭由程序打开的文件描述符,也不会调用任何已注册的退出处理函数(如atexit()注册的函数)。通常,`_exit()`用于快速结束程序,不关心这些额外的清理操作。
        exit():这个函数在终止进程之前会执行一系列清理操作。它会刷新所有打开文件的标准I/O缓冲区,关闭所有已打开的文件描述符,并调用所有已注册的退出处理函数。`exit()`函数还会将状态信息传递给父进程或者操作系统,以表示程序是正常退出还是异常退出。如果`exit()`是在`main()`函数中被隐式调用的(通过`return`语句),那么它会使用`main()`的返回值作为退出状态。

        总的来说,_exit()主要用于快速结束程序,而exit()则提供了更全面的清理功能,确保程序能够优雅地结束。

【正确答案:

6.exec函数族可以创建一个新的进程来执行指定的程序 (  )

exec函数族(如execlp()execv()execle()execve()execlp()execvp()等)并不创建一个新的进程来执行指定的程序,而是用指定的程序来替换当前进程的内存映像。换句话说,调用exec函数后,当前进程的代码、数据、堆和栈都会被新的程序替换,然后从新的程序的入口点开始执行。因此,从某种角度看,调用exec后原进程已经“消失”了,其PID并没有改变,但执行的是全新的程序。

要创建一个新的进程来执行指定的程序,应该首先使用fork()创建一个子进程,然后在子进程中调用exec()来执行新的程序。这样,父进程和子进程就可以并行执行不同的任务了。

【正确答案:×】

7.wait函数无法接收子进程退出的状态 (  )

wait函数是一个系统调用,用于使父进程等待子进程完成执行,并可以接收子进程退出的状态。wait函数不仅可以使父进程等待子进程的结束,还可以获取子进程的退出状态,是进程间同步和通信的重要工具。

【正确答案:×】

8.无名管道只能用于具有亲缘关系的进程间通信 (  )

无名管道(Unnamed Pipe)是UNIX系统中最早的进程间通信(IPC)形式之一,它允许两个进程之间进行数据交换。以下是无名管道的一些特点和限制:

        1. **半双工通信**:无名管道是半双工的,这意味着数据只能在一个方向上流动,即它有一个固定的读端和写端。
        2. **亲缘关系限制**:无名管道通常用于有亲缘关系的进程之间,如父子进程或兄弟进程之间的通信。这是因为在创建管道时,需要通过fork()系统调用来复制进程,从而使得父进程和子进程能够共享文件描述符,进而访问同一个管道文件的地址。
        3. **文件描述符共享**:无名管道依赖于父子进程间的文件描述符共享,因此它不适用于没有亲缘关系的进程间通信。在进程分离之前,可以在内核空间申请管道文件,分离后所有有亲缘关系的进程都可以通过复制的文件描述符访问同一个管道文件的地址。

总的来说,如果需要在没有亲缘关系的进程之间进行通信,可以使用有名管道(Named Pipe)或者也称为FIFO,它在文件系统中有文件节点,因此可以被任何知道其路径的进程访问。

【正确答案:

9.对命名管道的读写严格遵循先进先出的规则 (  )

命名管道,也被称为FIFO(first-in, first-out),在读写数据时严格遵循先进先出的规则。具体来说,写入FIFO的数据会被添加在队列的末尾,而读取FIFO的数据则会从队列的开头返回。这种机制确保了数据的顺序性和一致性,使得命名管道成为一种可靠的进程间通信方式。

【正确答案:

10.信号既可以发给前台进程也可以发给后台进程 (  )

在操作系统中,信号是进程之间事件异步通知的一种方式,属于软中断。无论是前台进程还是后台进程,都可以接收信号。例如,在Linux中,ctrl+c产生的SIGINT信号就可以发送给前台进程,也可以发送给在后台运行的进程(如果进程支持接收该信号的话)。当然,前台进程和后台进程在接收信号和处理信号的方式上可能有所不同,但这并不影响它们都可以接收信号的事实。

【正确答案:

11.可以用signal()向指定的进程发信号 (  )

在Unix和类Unix系统中,signal()函数通常用于设置信号处理函数,而不是直接用于向指定的进程发送信号。signal()函数用于改变进程接收到特定信号时的行为。你可以使用它来指定一个函数,当进程接收到某个信号时,该函数会被调用。

要向指定的进程发送信号,你应该使用kill()函数或raise()函数。kill()函数允许你向一个指定的进程ID(PID)发送信号,而raise()函数则用于向当前进程发送信号。

因此,signal()函数本身并不用于发送信号,而是用于设置信号的处理方式。

【正确答案:×】

12.无法用信号实现进程间的同步 (  )

信号机制**无法实现进程间的同步**,原因在于它主要用于进程间通信,而不是直接用于同步。

信号机制是Unix系统中的一种进程间通信方式,主要用来通知接收进程某个事件已经发生。当一个进程接收到一个信号后,它可以定义对该信号的处理方式,例如忽略信号、采取默认操作或执行特定的信号处理函数。然而,信号并不提供一种手段来保证发送和接收进程之间的同步,因为信号的送达时间是非确定的,并且信号处理函数的执行时机也可能影响程序的其它部分。

而进程间同步通常涉及到一组进程需要协调它们的行为,以确保它们在执行某些关键操作时能够保持一致性。常见的进程间同步机制包括:

1. **临界区(Critical Section)**: 通过串行化访问共享资源或代码段来控制数据访问,适合控制同一进程内线程的访问速度。
2. **互斥量(Mutex)**: 用于协调对共享资源的单独访问权限,可以跨进程使用,确保在任何时刻只有一个线程拥有访问权。
3. **信号量(Semaphore)**: 设计用来控制对有限数量用户资源的访问,允许多个线程在同一时刻访问同一资源,限制了同时访问的最大线程数目。
4. **事件(Event)**: 可以用来通知线程某些事件已发生,启动后继任务的开始,并可以实现不同进程之间的线程同步操作。

综上,信号机制虽然可以在一定程度上影响进程行为,但它不提供足够的控制机制来保证进程间的同步。真正实现进程间同步的机制需要考虑进程间协作的直接制约关系和资源共享的间接制约关系,确保进程在执行过程中的正确顺序和协调一致。

【正确答案:

13.消息队列可以按照消息类型读取消息 (  )

消息队列(Message Queue)是一种在分布式系统中进行异步通信的机制,它允许生产者将消息发送到队列中,然后由消费者从队列中接收并处理这些消息。消息队列的一个重要特性是它可以支持按照消息类型来读取消息。

这意味着消费者不需要按照消息进入队列的顺序(即先进先出,FIFO)来读取消息,而是可以根据自定义的条件或消息的类型来选择性地读取和处理消息。这种灵活性使得消息队列在处理复杂业务逻辑和异步通信场景时非常有用。

【正确答案:

14.消息队列的读写只能采用阻塞的方式 (  )

        消息队列的读写并非只能采用阻塞的方式。

        消息队列提供了灵活的通信机制,允许在任务(或进程)之间传递信息。它支持同步和异步的操作,这意味着读写操作可以以非阻塞的方式进行。

        读取操作:当一个任务尝试从队列中读取消息时,如果队列为空,则它可以进入阻塞状态,等待直到有消息可用。然而,也可以设置一个超时时间,在这个时间内如果队列仍然为空,则任务会解除阻塞并继续执行其他操作。此外,如果消息队列支持非阻塞(异步)读取,那么任务可以在没有消息的情况下立即返回,不进入阻塞状态。
        写入操作:当一个任务往满的消息队列中写消息时,可能会进入阻塞状态,等待直到队列中有空间可用。与读取操作类似,写入也可以设置超时时间,超过这个时间后,如果队列还是满的,任务会解除阻塞并可能返回错误。同样地,如果支持非阻塞写入,则任务可以在队列满的情况下立即返回,不进入阻塞状态。

        此外,消息队列还支持不同的排队原则,如先进先出(FIFO)和后进先出(LIFO),以及不同的优先级设置,这增加了它的灵活性。

        总的来说,消息队列提供了多种读写方式,包括阻塞和非阻塞(异步)。

【正确答案:×】

15.共享内存是一种最为高效的进程间通信方式 (  )

        共享内存允许两个或多个进程访问同一块内存空间,从而实现进程间的数据共享和通信。由于数据不需要在进程间复制,因此共享内存通常被认为是进程间通信(IPC)中最快的方式之一。它避免了内核和用户空间之间的数据拷贝,从而减少了通信开销。

        然而,共享内存也带来了一些挑战,如同步和互斥问题。当多个进程同时访问共享内存时,需要采取适当的同步机制来确保数据的一致性和完整性。此外,共享内存的管理也相对复杂,需要谨慎处理内存分配、释放和错误处理等问题。

        因此,虽然共享内存通常被认为是一种高效的进程间通信方式,但在具体使用时还需要根据应用场景和需求进行权衡和选择。

【正确答案:

三、简答题(总分30分)

1.请描述进程和程序的区别 (6分)

主要区别如下:

  1. 动态与静态
    • 程序(Program):是一个静态的概念,指的是一组计算机能识别和执行的指令集合,通常以某种程序设计语言编写,并存储于某种介质上。它描述了计算机应如何执行特定的任务,但并不直接涉及执行。
    • 进程(Process):是程序的一次执行过程,是动态的概念。当程序被加载到内存中并由操作系统调度执行时,它就被称为一个进程。进程是系统进行资源分配和调度的基本单位。
  2. 生命周期
    • 程序:一旦编写完成并存储于介质上,其存在就是永久的,除非被删除或修改。
    • 进程:其生命周期是有限的。它因创建而产生,因调度而执行,可能因得不到资源而暂停,最终因完成执行或错误而消亡。
  3. 组成结构
    • 程序:由指令、数据及其组织形式组成,它本身不包含执行时的系统状态信息。
    • 进程:由程序、数据和进程控制块(PCB)组成。PCB包含了系统用于描述进程状态和控制进程运行所需的信息,如程序计数器、CPU寄存器、进程状态等。
  4. 独立性
    • 程序:作为静态的指令集合,不具有独立性。
    • 进程:作为独立运行的实体,它拥有独立的地址空间、系统资源和执行环境,是系统进行资源分配和调度的独立单位。
  5. 并发性
    • 程序:本身并不具有并发性,它是顺序执行的指令集合。
    • 进程:在操作系统中,多个进程可以并发执行,即在同一时间段内,多个进程在系统中交替或重叠执行。
  6. 执行结果
    • 程序:在没有被执行时,不产生任何结果。
    • 进程:通过执行程序,可以产生结果,如输出数据、改变系统状态等。

综上所述,进程和程序的主要区别在于它们的动态与静态性质、生命周期、组成结构、独立性、并发性和执行结果等方面。

2.指出静态库和共享库的区别(使用方法,对程序的影响) (8分)
  1. 使用方法:
  • 静态库:在静态链接时使用,每个程序都有一份静态库的副本。这意味着当程序被编译时,静态库中的代码和数据会被直接复制到最终的可执行文件中。因此,使用静态库的程序在运行时不再需要静态库的存在。
  • 共享库:在动态链接时使用,多个程序可以共享同一个共享库。当程序被编译时,它并不直接包含共享库中的代码和数据,而是引用共享库中的函数和数据。在程序运行时,操作系统会负责将共享库加载到内存中,并将程序中的引用解析为共享库中的实际地址。
  1. 对程序的影响:
  • 静态库:
    • 优点:静态库的使用相对简单,因为它在编译时就已经将所需的代码和数据复制到可执行文件中,所以程序在运行时不再需要依赖外部库。此外,由于静态库中的代码和数据是嵌入到可执行文件中的,所以它可以提供更好的性能,因为避免了运行时动态链接的开销。
    • 缺点:静态库的一个主要缺点是它会显著增加可执行文件的大小。因为每个使用静态库的程序都需要包含一份完整的库副本,所以即使多个程序使用了相同的库函数,它们也会分别包含一份完整的库代码和数据。这会导致磁盘空间的浪费,并在多进程操作系统下浪费内存。另外,如果静态库更新了,所有使用它的应用程序都需要重新编译和发布。
  • 共享库:
    • 优点:共享库的主要优点是它可以显著减少磁盘空间和内存的使用。因为多个程序可以共享同一个共享库,所以它们不再需要各自包含一份完整的库副本。此外,共享库还使得程序更新变得更加容易。当共享库更新时,只需要重新编译和发布共享库本身,而不需要重新编译和发布所有使用它的程序。
    • 缺点:共享库的一个主要缺点是它可能会增加程序的复杂性。因为程序在运行时需要动态地链接到共享库,所以如果共享库中的函数或数据结构发生了更改,那么所有使用它的程序都需要进行相应的修改和重新编译。此外,如果多个程序同时访问同一个共享库,并且该库中的数据是可变的,那么就需要采取适当的同步措施来避免数据竞争和不一致性的问题。
3.写出设置信号处理函数signal的原型 (6分)

在Unix和类Unix系统中,signal函数用于设置进程接收到特定信号时的行为。它的原型在<signal.h>头文件中定义,如下所示:

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

其中:

  • signum 是要捕获的信号的编号(例如,SIGINTSIGTERM等)。
  • handler 是当接收到信号时要调用的函数的指针。这个函数通常被称为信号处理程序(signal handler)。如果handler被设置为SIG_IGN,则忽略该信号;如果设置为SIG_DFL,则恢复为系统默认的信号处理方式。

signal函数返回一个指向先前关联于signum的信号的信号处理函数的指针。如果出现错误,则返回SIG_ERR

需要注意的是,尽管signal函数在POSIX.1-1990标准中被定义,但由于它在处理多个信号时的语义在某些系统上可能不是可重入的(reentrant),因此在编写多线程程序或处理多个信号时,通常建议使用sigaction函数,它提供了更强大和更可靠的信号处理能力。

4.程序代码如下,请按执行顺序写出输出结果 (6分)

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
    pid_t pid1, pid2;

    if((pid1 = fork()) == 0)
    {
        sleep(3);
        printf("info1 from child process_1\n");
        exit(0);
        printf("info2 from child process_1\n");
    }
    else
    {
        if((pid2 = fork()) == 0)
        {
            sleep(1);
            printf("info1 from child process_2\n");
            exit(0);
        }
        else
        {
            wait(NULL);
            wait(NULL);
            printf("info1 from parent process\n");
            printf("info2 from parent process\n");
            _exit(0);
        }
    }
}

根据提供的程序代码和Unix/Linux进程创建与等待的机制,我们可以分析出程序的执行顺序和输出结果。

首先,主进程(父进程)会执行到第一个fork(),这将创建一个子进程(子进程1)。接下来有两种情况:

  1. 如果pid1为0,说明当前在子进程1中。子进程1会sleep(3)秒,然后打印"info1 from child process_1\n",接着调用exit(0)退出。由于exit(0)之后的代码不会被执行,所以"info2 from child process_1\n"不会被打印。

  2. 如果pid1不为0(即在父进程中),则父进程会执行到第二个fork(),这将创建另一个子进程(子进程2)。

    a. 如果pid2为0,说明当前在子进程2中。子进程2会sleep(1)秒,然后打印"info1 from child process_2\n",接着调用exit(0)退出。同样,"info2 from child process_2\n"不会被打印。

    b. 如果pid2不为0(即在父进程中),则父进程会等待两个子进程结束。由于子进程2的sleep时间比子进程1短,所以子进程2可能会先结束,但这不是确定的,因为操作系统的调度策略可能会影响进程的执行顺序。

    父进程会调用两次wait(NULL)来等待两个子进程结束。由于wait(NULL)会阻塞父进程直到有一个子进程结束,所以父进程会先等待一个子进程结束,然后再等待另一个子进程结束。

    最后,父进程会打印"info1 from parent process\n"和"info2 from parent process\n",然后调用_exit(0)退出。

因此,可能的输出顺序是:

  1. 如果子进程2先结束:
    • "info1 from child process_2\n"(子进程2打印)
    • "info1 from child process_1\n"(子进程1打印)
    • "info1 from parent process\n"(父进程打印)
    • "info2 from parent process\n"(父进程打印)
  2. 如果子进程1先结束(尽管它应该等待更长时间,但这取决于操作系统的调度):
    • "info1 from child process_1\n"(子进程1打印)
    • "info1 from child process_2\n"(子进程2打印)
    • "info1 from parent process\n"(父进程打印)
    • "info2 from parent process\n"(父进程打印)

注意,尽管在代码中两次调用了wait(NULL),但由于父进程会等待两个子进程都结束,所以两次调用是有效的。此外,由于并发性和操作系统的调度策略,子进程1和子进程2的结束顺序是不确定的。

5.列出任意四种进程间通信的方式(4分)

进程间通信(IPC)是操作系统中不同进程之间传递信息或数据的一种机制。以下是四种常见的进程间通信方式:

  1. 管道(Pipe)
    • 管道是最早出现的IPC形式之一,通常指无名管道(匿名管道)。
    • 它只能用于具有亲缘关系的进程之间的通信,例如父子进程之间。
    • 管道是半双工的,数据只能单向流动。
    • 有名管道(FIFO)允许无亲缘关系的进程间通信。
  2. 消息队列(Message Queue)
    • 消息队列是消息的链接表,存放在内核中。
    • 它克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    • 消息队列允许进程以消息的格式(具有特定的格式和属性)来发送和接收数据。
    • 有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。
  3. 共享内存(Shared Memory)
    • 共享内存允许多个进程访问同一块内存空间。
    • 它是所有IPC方式中最快的一种,因为数据不需要在不同的进程间复制。
    • 但由于多个进程可以同时操作共享内存,所以需要进行同步以避免数据冲突。
  4. 信号(Signal)
    • 信号是一种异步的通知机制,用于通知进程有某种事件发生。
    • 除了用于进程间通信外,进程还可以发送信号给进程本身。
    • 信号量(Semaphore)是一种特殊的信号,它主要用于控制多个进程对共享资源的访问,以实现进程间的同步和互斥。

四、问答题(25分)

1.指出创建守护进程的步骤(5分)

创建守护进程的步骤主要包括以下几个方面:

  1. 创建子进程,并退出父进程:这是创建守护进程的第一步。由于守护进程是脱离控制终端的,因此首先创建子进程,并终止父进程,使得程序在Shell终端里造成一个已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他的命令,从而使得程序以孤儿进程形式运行,在形式上做到了与控制终端的脱离。
  2. 在子进程中创建新会话:这是创建守护进程中最重要的一步。可以使用系统函数setsid来创建一个新的会话,并担任该会话组的组长。setsid函数的作用是使进程摆脱原会话的控制、摆脱原进程组的控制以及摆脱原控制终端的控制。
  3. 改变工作目录:使用fork创建的子进程会继承父进程的当前工作目录。为了避免在进程运行过程中,当前目录所在的文件系统被卸载,通常会将当前工作目录换成其他的路径,如“/”或“/tmp”等。这可以通过chdir函数来实现。
  4. 重设文件创建掩码:文件创建掩码是指屏蔽掉文件创建时的对应位。由于使用fork函数新建的子进程继承了父进程的文件创建掩码,这可能会给该子进程使用文件带来麻烦。因此,通常会将文件创建掩码设置为0。
  5. 关闭其他文件描述符:关闭不再需要的文件描述符,以避免潜在的资源泄露问题。

完成以上步骤后,守护进程就已经成功创建了。守护进程会在系统后台运行,并且不受任何终端的控制,用于执行特定的系统任务。很多守护进程在系统引导时启动,并且一直运行直到系统关闭。

2.请画出Linux中进程的状态切换图(5分)

3.用标准IO的fgets函数计算一个文件的行数(写核心代码即可)(5分)

使用标准I/O的fgets函数来计算一个文件的行数,你可以通过逐行读取文件内容直到到达文件末尾来实现。每次fgets成功读取一行时,行数就增加1。以下是一个简单的C语言代码示例,展示了如何做到这一点:

#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
printf("Usage: %s <filename>\n", argv[0]);
return 1;
}
FILE *file = fopen(argv[1], "r");
if (file == NULL) {
perror("Error opening file");
return 1;
}
char line[1024]; // 假设每行不超过1023个字符(加上换行符)
int line_count = 0;
while (fgets(line, sizeof(line), file) != NULL) {
line_count++;
}
fclose(file);
printf("Number of lines in the file: %d\n", line_count);
return 0;
}

这段代码首先检查命令行参数的数量,确保提供了一个文件名。然后,它打开该文件以进行读取。接下来,它定义了一个字符数组line来存储从文件中读取的每一行(假设每行不超过1023个字符,加上一个额外的字符用于存储换行符或字符串终止符)。然后,它进入一个循环,使用fgets函数逐行读取文件内容,每次成功读取一行时,line_count就增加1。最后,它关闭文件并打印出行数。

4.编写程序实现如下功能(10分)

reader.c     从argv[1]所指定的文件中读取内容,依次写到管道/home/linux/myfifo中

writer.c     从管道/home/linux/myfifo中读取内容,写到argv[1]所指定的文件中并保存

代码中可省略头文件,/home/linux/myfifo无需创建

reader.c

int main(int argc, char *argv[]) {
    int fd;
    char buffer[1024];
    ssize_t bytesRead;

    if (argc != 2) {
        printf("Usage: %s <filename>\n", argv[0]);
        return 1;
    }

    fd = open(argv[1], O_RDONLY);
    if (fd == -1) {
        perror("open");
        return 1;
    }

    int fifo = open("/home/linux/myfifo", O_WRONLY);
    if (fifo == -1) {
        perror("open");
        close(fd);
        return 1;
    }

    while ((bytesRead = read(fd, buffer, sizeof(buffer))) > 0) {
        write(fifo, buffer, bytesRead);
    }

    close(fd);
    close(fifo);

    return 0;
}

writer.c

int main(int argc, char *argv[]) {
    int fd;
    char buffer[1024];
    ssize_t bytesRead;

    if (argc != 2) {
        printf("Usage: %s <filename>\n", argv[0]);
        return 1;
    }

    int fifo = open("/home/linux/myfifo", O_RDONLY);
    if (fifo == -1) {
        perror("open");
        return 1;
    }

    fd = open(argv[1], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR);
    if (fd == -1) {
        perror("open");
        close(fifo);
        return 1;
    }

    while ((bytesRead = read(fifo, buffer, sizeof(buffer))) > 0) {
        write(fd, buffer, bytesRead);
    }

    close(fd);
    close(fifo);

    return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值