Linux应用程序开发:进程的一些事儿

这篇具有很好参考价值的文章主要介绍了Linux应用程序开发:进程的一些事儿。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、进程的简介

1、什么是进程,进程的概念

  进程是一个动态过程,而非静态文件,它是程序的一次运行过程,当应用程序被加载到内存中运行之后它就称为了一个进程,当程序运行结束后也就意味着进程终止,这就是进程的一个生命周期。

2、进程状态

  Linux 系统下进程通常存在 6 种不同的状态,分为:就绪态、运行态、僵尸态、 可中断睡眠状态(浅度睡眠)、不可中断睡眠状态(深度睡眠)以及暂停态。
  ⚫ 就绪态(Ready) : 指该进程满足被 CPU 调度的所有条件但此时并没有被调度执行,只要得到 CPU就能够直接运行;意味着该进程已经准备好被 CPU 执行,当一个进程的时间片到达,操作系统调度程序会从就绪态链表中调度一个进程;
  ⚫ 运行态: 指该进程当前正在被 CPU 调度运行,处于就绪态的进程得到 CPU 调度就会进入运行态;
  ⚫ 僵尸态: 僵尸态进程其实指的就是僵尸进程,指该进程已经结束、但其父进程还未给它“收尸”;
  ⚫ 可中断睡眠状态: 可中断睡眠也称为浅度睡眠,表示睡的不够“死”,还可以被唤醒,一般来说可以通过信号来唤醒;
  ⚫ 不可中断睡眠状态: 不可中断睡眠称为深度睡眠,深度睡眠无法被信号唤醒,只能等待相应的条件成立才能结束睡眠状态。把浅度睡眠和深度睡眠统称为等待态(或者叫阻塞态) ,表示进程处于一种等待状态,等待某种条件成立之后便会进入到就绪态;所以,处于等待态的进程是无法参与进程系统调度的。
  ⚫ 暂停态: 暂停并不是进程的终止,表示进程暂停运行,一般可通过信号将进程暂停,譬如 SIGSTOP信号;处于暂停态的进程是可以恢复进入到就绪态的,譬如收到 SIGCONT 信号。一个新创建的进程会处于就绪态,只要得到 CPU 就能被执行。以下列出了进程各个状态之间的转换关系,如下所示:
Linux应用程序开发:进程的一些事儿

3、什么是进程号

  Linux 系统下的每一个进程都有一个进程号(process ID,简称 PID),进程号是一个正数,用于唯一标
识系统中的某一个进程。

4、进程间的通信方法(IPC)

  ①管道通信:分别为有名管道和无名管道。
  ②信号。
  ③共享内存。
  ④消息队列。
  ⑤信号量。
  ⑥套接字(socket)。

二、 fork()创建子进程

Linux应用程序开发:进程的一些事儿

三、父、 子进程间的文件共享

  调用 fork()函数之后,子进程会获得父进程所有文件描述符的副本,这些副本的创建方式类似于 dup(),
这也意味着父、子进程对应的文件描述符均指向相同的文件表,如下图所示:
Linux应用程序开发:进程的一些事儿

  由此可知,子进程拷贝了父进程的文件描述符表,使得父、子进程中对应的文件描述符指向了相同的文件表, 也意味着父、子进程中对应的文件描述符指向了磁盘中相同的文件,因而这些文件在父、子进程间实现了共享,譬如,如果子进程更新了文件偏移量,那么这个改变也会影响到父进程中相应文件描述符的位置偏移量。

1、实验1

  父进程打开文件之后,然后 fork()创建子进程,此时子进程继承了父进程打开的文件描述符(父进程文件描述符的副本) ,然后父、子进程同时对文件进行写入操作。
Linux应用程序开发:进程的一些事儿
  上图测试结果可知,此种情况下,父、子进程分别对同一个文件进行写入操作,结果是接续写,不管是父进程,还是子进程,在每次写入时都是从文件的末尾写入, 很像使用了 O_APPEND 标志的效果。 其原因也非常简单, 图 9.6.1 中便给出了答案,子进程继承了父进程的文件描述符,两个文件描述符都指向了一个相同的文件表,意味着它们的文件偏移量是同一个、绑定在了一起,相互影响,子进程改变了文件的位置偏移量就会作用到父进程,同理,父进程改变了文件的位置偏移量就会作用到子进程。

2、实验2

  父进程在调用 fork()之后,此时父进程和子进程都去打开同一个文件,然后再对文件进行写入操作。
Linux应用程序开发:进程的一些事儿
  上图测试结果可知,这种文件共享方式实现的是一种两个进程分别各自对文件进行写入操作,因为父、子进程的这两个文件描述符分别指向的是不同的文件表,意味着它们有各自的文件偏移量,一个进程修改了文件偏移量并不会影响另一个进程的文件偏移量,所以写入的数据会出现覆盖的情况。

四、使用execl函数执行新程序

  当子进程的工作不再是运行父进程的代码段,而是运行另一个新程序的代码,那么这个时候子进程可以通过 exec 函数来实现运行另一个新的程序。而execl函数是exec族函数里面的其中一个员,但它是比较常使用的那个。
  1、先创建新的程序hello.c文件。
Linux应用程序开发:进程的一些事儿
  2、创建一个execl.c文件,功能:创建子进程,并且将子进程继承的父进程的程序替换成新的程序,并运行。
Linux应用程序开发:进程的一些事儿
  上图测试现象可知,execl函数是可以替换子进行的程序,然后执行新的程序的。

五、关于终端上对进程的一些指令操作

  指令:ps -aux
  其功能是查看系统进程的详细信息。

  指令: | grep
  其功能是管道筛选信息。

  指令:kill -9 [pid]
  其功能是杀死,结束对应进程。

六、孤儿进程

  父进程先于子进程结束,也就是意味着,此时子进程变成了一个“孤儿”,我们把这种进程就称为孤儿进程;因为子进程在结束后需要父进程将子进程的一些资源释放掉,而父进程先于子进程结束的话,那么系统就会让init进程接管父进程职责,释放结束后的子进程资源。 在 Linux 系统当中,所有的孤儿进程都自动成为 init 进程(进程号为 1)的子进程, 换言之,某一子进程的父进程结束后,该子进程调用 getppid()将返回 1, init 进程变成了孤儿进程的“养父”。

  孤儿进程程序实现与测试:
Linux应用程序开发:进程的一些事儿
  由上图测试结果可知,在红色框框中,父进程还未结束;在黄色框框中,由于子进程sleep阻塞了3秒,父进程先于子进程结束了,然后子进程的父进程(pid=10604)变成了父进程(pid=2122),在右边框框可知,pid:2122是/sbin/upstart其实就是init进程。

七、僵尸进程

  进程结束之后,通常需要其父进程为其“收尸”,回收子进程占用的一些内存资源,父进程通过调用wait()(或其变体 waitpid()、 waitid()等)函数回收子进程资源,归还给系统。
  如果子进程先于父进程结束,此时父进程还未来得及给子进程“收尸”,那么此时子进程就变成了一个僵尸进程。 子进程结束后其父进程并没有来得及立马给它“收尸”, 子进程处于“曝尸荒野”的状态,在这么一个状态下,我们就将子进程成为僵尸进程;至于名字由来,肯定是对电影情节的一种效仿!
  当父进程调用 wait()(或其变体,下文不再强调)为子进程“收尸”后,僵尸进程就会被内核彻底删除。另外一种情况,如果父进程并没有调用 wait()函数然后就退出了,那么此时 init 进程将会接管它的子进程并自动调用 wait(), 故而从系统中移除僵尸进程。
  如果父进程创建了某一子进程,子进程已经结束,而父进程还在正常运行,但父进程并未调用 wait()回收子进程,此时子进程变成一个僵尸进程。 首先来说,这样的程序设计是有问题的,如果系统中存在大量的僵尸进程,它们势必会填满内核进程表,从而阻碍新进程的创建。需要注意的是,僵尸进程是无法通过信号将其杀死的,即使是“一击必杀”信号 SIGKILL 也无法将其杀死,那么这种情况下,只能杀死僵尸进程的父进程(或等待其父进程终止),这样 init 进程将会接管这些僵尸进程,从而将它们从系统中清理掉!所以,在我们的一个程序设计中,一定要监视子进程的状态变化,如果子进程终止了,要调用 wait()将其回收,避免僵尸进程。

  僵尸进程程序实现与测试:
Linux应用程序开发:进程的一些事儿
  通过命令可以查看到子进程 10997 依然存在,可以看到它的状态栏显示的是“Z”(zombie,僵尸),表示它是一个僵尸进程。 僵尸进程无法被信号杀死,大家可以试试,要么等待其父进程终止、要么杀死其父进程,让 init 进程来处理,当我们杀死其父进程之后,僵尸进程也会被随之清理。

1、监视子进程

  在很多应用程序的设计中,父进程需要知道子进程于何时被终止,并且需要知道子进程的终止状态信息,是正常终止、还是异常终止亦或者被信号终止等,意味着父进程会对子进程进行监视。而这可以通过系统调用 wait()以及其它变体来监视子进程的状态改变。

  wait()函数:
  对于许多需要创建子进程的进程来说,有时设计需要监视子进程的终止时间以及终止时的一些状态信息,在某些设计需求下这是很有必要的。 系统调用 wait()可以等待进程的任一子进程终止,同时获取子进程的终止状态信息,其函数原型如下所示:
Linux应用程序开发:进程的一些事儿
Linux应用程序开发:进程的一些事儿

  使用wait函数监视子进程程序实现与测试:
Linux应用程序开发:进程的一些事儿
  实验现象可知,父进程也是经过2秒的等待后才进行对子进程收尸处理的,说明wait()函数能起到监视和减少子进程变成僵尸进程发生。

八、守护进程

1、何为守护进程

  守护进程(Daemon) 也称为精灵进程,是运行在后台的一种特殊进程,它独立于控制终端并且周期性地执行某种任务或等待处理某些事情的发生, 主要表现为以下两个特点:
  ⚫ 长期运行。 守护进程是一种生存期很长的一种进程,它们一般在系统启动时开始运行,除非强行终止,否则直到系统关机都会保持运行。 与守护进程相比,普通进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但守护进程不受用户登录注销的影响,它们将会一直运行着、直到系统关机。
  ⚫ 与控制终端脱离。 在 Linux 中,系统与用户交互的界面称为终端,每一个从终端开始运行的进程都会依附于这个终端,这是上一小节给大家介绍的控制终端,也就是会话的控制终端。当控制终端被关闭的时候, 该会话就会退出, 由控制终端运行的所有进程都会被终止, 这使得普通进程都是和运行该进程的终端相绑定的; 但守护进程能突破这种限制,它脱离终端并且在后台运行, 脱离终端的目的是为了避免进程在运行的过程中的信息在终端显示并且进程也不会被任何终端所产生的信息所打断。
  守护进程是一种很有用的进程。 Linux 中大多数服务器就是用守护进程实现的,譬如, Internet 服务器inetd、 Web 服务器 httpd 等。同时,守护进程完成许多系统任务,譬如作业规划进程 crond 等。
  守护进程 Daemon,通常简称为 d,一般进程名后面带有 d 就表示它是一个守护进程。守护进程与终端无任何关联,用户的登录与注销与守护进程无关、不受其影响,守护进程自成进程组、自成会话,即pid=gid=sid。通过命令"ps -ajx"查看系统所有的进程,如下所示:
Linux应用程序开发:进程的一些事儿
  TTY 一栏是问号?表示该进程没有控制终端,也就是守护进程,其中 COMMAND 一栏使用中括号[]括起来的表示内核线程,这些线程是在内核里创建,没有用户空间代码,因此没有程序文件名和命令行,通常采用 k 开头的名字,表示 Kernel。

2、编写守护进程程序

  编写守护进程一般包含如下几个步骤:
  1) 创建子进程、终止父进程
  父进程调用 fork()创建子进程,然后父进程使用 exit()退出,这样做实现了下面几点。第一,如果该守护进程是作为一条简单地 shell 命令启动,那么父进程终止会让 shell 认为这条命令已经执行完毕。第二,虽然子进程继承了父进程的进程ID,但它有自己独立的进程ID,这保证了子进程不是一个进程组的组长进程,这是下面将要调用 setsid 函数的先决条件!
  2) 子进程调用 setsid 创建会话
  这步是关键,由于之前子进程并不是进程组的组长进程,所以调用 setsid()会使得子进程创建一个新的会话,子进程成为新会话的首领进程,同样也创建了新的进程组、子进程成为组长进程,此时创建的会话将没有控制终端。 所以这里调用 setsid 有三个作用:让子进程摆脱原会话的控制、让子进程摆脱原进程组的控制和让子进程摆脱原控制终端的控制。在调用 fork 函数时,子进程继承了父进程的会话、进程组、控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没有改变,因此,那还不是真正意义上使两者独立开来。 setsid 函数能够使子进程完全独立出来,从而脱离所有其他进程的控制。
  3) 将工作目录更改为根目录
  子进程是继承了父进程的当前工作目录,由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后使用会造成很多的麻烦。因此通常的做法是让“/”作为守护进程的当前目录,当然也可以指定其它目录来作为守护进程的工作目录。
  4) 重设文件权限掩码 umask
  文件权限掩码 umask 用于对新建文件的权限位进行屏蔽。由于使用 fork 函数新建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了诸多的麻烦。因此, 把文件权限掩码设置为 0, 确保子进程有最大操作权限、这样可以大大增强该守护进程的灵活性。设置文件权限掩码的函数是 umask,通常的使用方法为 umask(0)。
  5) 关闭不再需要的文件描述符
  子进程继承了父进程的所有文件描述符, 这些被打开的文件可能永远不会被守护进程(此时守护进程指的就是子进程,父进程退出、子进程成为守护进程) 读或写,但它们一样消耗系统资源,可能导致所在的文件系统无法卸载,所以必须关闭这些文件, 这使得守护进程不再持有从其父进程继承过来的任何文件描述符。
  6) 将文件描述符号为 0、 1、 2 定位到/dev/null
  将守护进程的标准输入、标准输出以及标准错误重定向到/dev/null,这使得守护进程的输出无处显示、也无处从交互式用户那里接收输入。
  7) 其它:忽略 SIGCHLD 信号
  处理 SIGCHLD 信号不是必须的, 但对于某些进程,特别是并发服务器进程往往是特别重要的,服务器进程在接收到客户端请求时会创建子进程去处理该请求,如果子进程结束之后,父进程没有去 wait 回收子进程,则子进程将成为僵尸进程;如果父进程 wait 等待子进程退出,将又会增加父进程的负担、也就是增加服务器的负担,影响服务器进程的并发性能,在 Linux 下,可以将 SIGCHLD 信号的处理方式设置为SIG_IGN,也就是忽略该信号,可让内核将僵尸进程转交给 init 进程去处理,这样既不会产生僵尸进程、又省去了服务器进程回收子进程所占用的时间。
Linux应用程序开发:进程的一些事儿

  从上图可知, daemon进程成为了一个守护进程,与控制台脱离,当关闭当前控制终端时,daemon进程并不会受到影响,依然会正常继续运行;
  守护进程可以通过终端命令行启动,但通常它们是由系统初始化脚本进行启动,譬如/etc/rc*或/etc/init.d/*等。

补充知识点:
Linux应用程序开发:进程的一些事儿

九、进程间的通信方法实现

1、管道通信

①无名管道通信

  pipe无名管道通信只能作用在有亲缘关系的进程之间通信,详情如下图所示:
Linux应用程序开发:进程的一些事儿
  pipe是一种单向数据传输的管道,其参数pipefd[2]是个整形数组,而pipefd[0]和pipefd[1]分别是创建的读管道的文件描述符和写管道的文件描述符。

  使用pipe函数实现父进程与子进程间的通信及测试:
Linux应用程序开发:进程的一些事儿
  从上图可知,当子进程读管道数据时,如果管道没有数据就会一直阻塞。只有管道有数据可读,子进程才会通,如下图所示:
Linux应用程序开发:进程的一些事儿
  在父进程中向管道写入数据,然后子进程向管道读取数据。

②有名管道通信

  mkfifo有名管道通信可以作用在无亲缘关系的进程之间通信,详情如下图所示:
Linux应用程序开发:进程的一些事儿
  mkfifo其实就是创建了一个实实在在的fifo文件,然后进程间的通信就是通过对创建的fifo文件进行操作,其fifo文件和块设备文件一样,都是不占用字节的,如下图所示:
Linux应用程序开发:进程的一些事儿

  使用mkfifo函数实现两个无亲缘关系的进程间的通信及测试:
  读fifo数据的进程程序:
Linux应用程序开发:进程的一些事儿
  写fifo数据的进程程序:
Linux应用程序开发:进程的一些事儿
  现象:
Linux应用程序开发:进程的一些事儿

2、信号通信

  信号的基本概念:信号是事件发生时对进程的通知机制,也可以把它称为软件中断。信号与硬件中断的相似之处在于能够打断程序当前执行的正常流程, 其实是在软件层次上对中断机制的一种模拟。 大多数情况下,是无法预测信号达到的准确时间,所以,信号提供了一种处理异步事件的方法。

信号的目的是用来通信的

  一个具有合适权限的进程能够向另一个进程发送信号,信号的这一用法可作为一种同步技术,甚至是进程间通信(IPC)的原始形式。 信号可以由“谁”发出呢? 以下列举的很多情况均可以产生信号:
  ⚫ 硬件发生异常,即硬件检测到错误条件并通知内核,随即再由内核发送相应的信号给相关进程。硬件检测到异常的例子包括执行一条异常的机器语言指令,诸如,除数为 0、数组访问越界导致引用了无法访问的内存区域等,这些异常情况都会被硬件检测到,并通知内核、然后内核为该异常情况发生时正在运行的进程发送适当的信号以通知进程。
  ⚫ 用于在终端下输入了能够产生信号的特殊字符。 譬如在终端上按下 CTRL + C 组合按键可以产生中断信号(SIGINT),通过这个方法可以终止在前台运行的进程;按下 CTRL + Z 组合按键可以产生暂停信号(SIGCONT),通过这个方法可以暂停当前前台运行的进程。
  ⚫ 进程调用 kill()系统调用可将任意信号发送给另一个进程或进程组。 当然对此是有所限制的,接收信号的进程和发送信号的进程的所有者必须相同,亦或者发送信号的进程的所有者是 root 超级用户。
  ⚫ 用户可以通过 kill 命令将信号发送给其它进程。 kill 命令想必大家都会使用,通常我们会通过 kill命令来“杀死”(终止)一个进程,譬如在终端下执行"kill -9 xxx"来杀死 PID 为 xxx 的进程。 kill命令其内部的实现原理便是通过 kill()系统调用来完成的。
  ⚫ 发生了软件事件,即当检测到某种软件条件已经发生。 这里指的不是硬件产生的条件(如除数为 0、引用无法访问的内存区域等),而是软件的触发条件、触发了某种软件条件(进程所设置的定时器已经超时、进程执行的 CPU 时间超限、进程的某个子进程退出等等情况)。
  进程同样也可以向自身发送信号,然而发送给进程的诸多信号中,大多数都是来自于内核。
  以上便是可以产生信号的多种不同的条件,总的来看,信号的目的都是用于通信的,当发生某种情况下,通过信号将情况“告知”相应的进程,从而达到同步、通信的目的。

信号由谁处理、怎么处理

  信号通常是发送给对应的进程,当信号到达后, 该进程需要做出相应的处理措施,通常进程会视具体信号执行以下操作之一:
  ⚫ 忽略信号。也就是说,当信号到达进程后,该进程并不会去理会它、直接忽略,就好像是没有出该信号,信号对该进程不会产生任何影响。事实上,大多数信号都可以使用这种方式进行处理,但有两种信号却决不能被忽略,它们是 SIGKILL 和 SIGSTOP,这两种信号不能被忽略的原因是:它们向内核和超级用户提供了使进程终止或停止的可靠方法。另外,如果忽略某些由硬件异常产生的信号,则进程的运行行为是未定义的。
  ⚫ 捕获信号。 当信号到达进程后,执行预先绑定好的信号处理函数。为了做到这一点,要通知内核在某种信号发生时,执行用户自定义的处理函数,该处理函数中将会对该信号事件作出相应的处理,Linux 系统提供了 signal()系统调用可用于注册信号的处理函数。
  ⚫ 执行系统默认操作。 进程不对该信号事件作出处理,而是交由系统进行处理,每一种信号都会有其对应的系统默认的处理方式。需要注意的是,对大多数信号来说,系统默认的处理方式就是终止该进程。

信号是异步的

  信号是异步事件的经典实例,产生信号的事件对进程而言是随机出现的,进程无法预测该事件产生的准确时间,进程不能够通过简单地测试一个变量或使用系统调用来判断是否产生了一个信号,这就如同硬件中断事件,程序是无法得知中断事件产生的具体时间,只有当产生中断事件时,才会告知程序、然后打断当前程序的正常执行流程、跳转去执行中断服务函数,这就是异步处理方式。


  信号本质上是 int 类型数字编号,这些信号在<signum.h>头文件中定义, 每个信号都是以 SIGxxx 开头,这里就自己去查了。


①信号的分类

Ⅰ、可靠信号与不可靠信号:

  不可靠信号主要指的是进程可能对信号做出错误的反应以及信号可能丢失(处理信号时又来了新的信号,则导致信号丢失)。
  在 Linux 系统下使用"kill -l"命令可查看到所有信号,如下所示:
Linux应用程序开发:进程的一些事儿
  括号" ) "前面的数字对应该信号的编号,编号 1~31 所对应的是不可靠信号,编号 34~64 对应的是
可靠信号,从图中可知,可靠信号并没有一个具体对应的名字,而是使用了 SIGRTMIN+N 或 SIGRTMAXN 的方式来表示。

Ⅱ、实时信号与非实时信号:

  实时信号与非实时信号其实是从时间关系上进行的分类,与可靠信号与不可靠信号是相互对应的, 非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。 实时信号保证了发送的多个信号都能被接收, 实时信号是 POSIX 标准的一部分,可用于应用进程。

②信号的发送函数及使用测试实验

Ⅰ、raise()函数

  可以在终端中敲man 3 raise可知raise函数的功能,如下图所示:
Linux应用程序开发:进程的一些事儿
  由上图可知,raise(sig)等价于kill(getpid(), sig),也就是说其功能就是自己给自己发送信号(实质是先将信号和pid号发给内核,内核再发送该信号给对应的pid进程或直接处理)。
  这里我做一个简单自己杀死自己的实验,如下图所示:
Linux应用程序开发:进程的一些事儿

Ⅱ、kill()函数

  这里不过多介绍该函数了,可以在man手册查询。
  这里我做一个简单 “进程kill” 杀死手动指定的 “进程a” 的实验,如下图所示:
Linux应用程序开发:进程的一些事儿
Linux应用程序开发:进程的一些事儿

Ⅱ、alarm()函数

  这里不过多介绍该函数了,可以在man手册查询。
  这里我做一个简单闹钟定时程序,当时间到了发出闹钟时间就终止程序的实验,如下图所示:
Linux应用程序开发:进程的一些事儿

③信号的接收函数及使用测试实验

Ⅰ、pause()函数

  pause()系统调用可以使得进程暂停运行、进入休眠状态,直到进程捕获到一个信号为止,详细信息可以可以在man手册查询。
  这里我做一个简单闹钟定时使程序退出pause状态,这次闹钟程序不是用默认的信号处理函数,当时间到了发出闹钟时间就触发pause函数,让程序继续往下走,如下图所示:
Linux应用程序开发:进程的一些事儿

④信号的处理函数及使用测试实验

Ⅰ、signal()函数

  当进程接收到内核或用户发送过来的信号之后,根据具体信号可以采取不同的处理方式:忽略信号、捕获信号或者执行系统默认操作。
  忽略信号:
Linux应用程序开发:进程的一些事儿
  系统默认:
Linux应用程序开发:进程的一些事儿
  捕获信号:
Linux应用程序开发:进程的一些事儿

3、共享内存

①共享内存的概念

  共享内存就是映射一段能被其它进程所访问的内存, 这段共享内存由一个进程创建, 但其它的多个进程都可以访问, 使得多个进程可以访问同一块内存空间。共享内存是最快的 IPC 方式, 它是针对其它进程间通信方式运行效率低而专门设计的, 它往往与其它通信机制, 譬如结合信号量来使用, 以实现进程间的同步和通信。

②shmget函数和ftok函数

Ⅰ、shmget函数

  这里我截取了man手册的描述,它是一个向系统申请分配一个共享内存空间的函数,如下图所示:
Linux应用程序开发:进程的一些事儿
注意:参数key值除了可以使用文档描述的 IPC_PRIVATE 这个宏外,还可以使用ftok函数获取这个key值

Ⅱ、ftok函数

Linux应用程序开发:进程的一些事儿

Ⅲ、shmat、shmdt、shmctl函数

  上述说过,使用shmget函数申请分配共享内存成功后,它会返回共享内存标识符,它和文件标识符是类似的,但是这个不便于我们去操作这个共享内存,所以在Linux应用中经常会使用共享内存映射到进程地址上,方便开发者直接对共享内存进行读写操作。
  shmat、shmdt、shmctl函数分别是创建共享内存映射、删除共享内存映射(但不会删除内核中的共享内存对象)、操作共享内存对象。

Ⅳ、使用shmget函数实现以共享内存方式的父子进程间的通信及测试实验

  我们先了解一下,使用系统自带的密钥IPC_PRIVATE和使用ftok函数自己获取的密钥 向系统申请分配共享内存的区别,图下两张图的实验所示:
Linux应用程序开发:进程的一些事儿


Linux应用程序开发:进程的一些事儿
  由上面两张实验结果对比可知,使用系统自带的密钥IPC_PRIVATE和使用ftok函数自己获取的密钥 向系统申请分配共享内存的区别前者共享内存描述符是为0的,而后者的共享描述符是不为0的。
  好,有了上面的了解,正式做要实现的实验,如下图所示:
Linux应用程序开发:进程的一些事儿

4、消息队列

①消息队列的概述

  消息队列是消息的链表, 存放在内核中并由消息队列标识符标识, 消息队列克服了信号传递信息少、 管道只能承载无格式字节流以及缓冲区大小受限等缺陷。 消息队列包括 POSIX 消息队列和 System V 消息队列。

  而 POSIX 消息队列和 System V 消息队列异同点:

目的不同:POSIX 消息队列主要用于进程间通信,而 System V 消息队列可以进行进程间和线程间的通信。
接口不同:POSIX 消息队列使用 mq_open(), mq_send(), mq_receive()等函数进行操作,而 System V 消息队列主要使用msgget(), msgsnd(), msgrcv()等函数进行操作。
大小限制不同:POSIX 消息队列没有固定的消息大小限制,但是 System V 消息队列有固定的消息大小限制。
使用方式不同:POSIX 消息队列的使用方式更为灵活,可以使用文件描述符来操作消息队列;而 System V 消息队列需要通过>键值来找到消息队列。
发送和接收消息的方式不同:POSIX 消息队列可以设置优先级,支持非阻塞方式和阻塞方式;而 System V 消息队列仅支持阻
塞方式,且不支持消息的优先级。
删除消息队列方式不同:POSIX 消息队列可以使用 mq_close()和mq_unlink()函数删除消息队列,而删除 System V 消息队列需>要使用 msgctl()函数。
相同点:都是进程通信的一种手段。都可以实现消息的发送和接收,保证进程间的异步通信。都可以使用信号量机制实现消息的>同步和互斥。

  消息队列是 UNIX 下不同进程之间实现共享资源的一种机制, UNIX 允许不同进程将格式化的数据流以消息队列形式发送给任意进程, 有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。

②使用消息队列实现两个无情缘关系之间的进程通信

Ⅰ、关于消息队列创建时用的key值的知识

Linux应用程序开发:进程的一些事儿
Linux应用程序开发:进程的一些事儿

Ⅱ、实现实验及测试

程序:
Linux应用程序开发:进程的一些事儿
结果:
Linux应用程序开发:进程的一些事儿

5、信号量

①信号量的概述

  信号量是一个计数器, 与其它进程间通信方式不大相同, 它主要用于保护临界资源,控制多个进程间或一个进程内的多个线程间对共享资源的访问, 相当于内存中的标志,进程可以根据它判定是否能够访问某些共享资源,同时,进程也可以修改该标志, 除了用于共享资源的访问控制外,还可用于进程同步。
  它常作为一种锁机制, 防止某进程在访问资源时其它进程也访问该资源, 因此, 主要作为进程间以及同一个进程内不同线程之间的同步手段。 Linux 提供了一组精心设计的信号量接口来对信号量进行操作,它们声明在头文件 sys/sem.h 中。

②使用信号量实现进程间的通信

Ⅰ、使用sleep阻塞方式,使父进程先于子进程运行

Linux应用程序开发:进程的一些事儿

Ⅱ、在上一个实验基础上,使用信号量的阻塞方式,使子进程先于父进程运行

semaphore.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/wait.h>

//信号量操作函数semctl中参数2联合体
union semun {
    int              val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short  *array;  /* Array for GETALL, SETALL */
    struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
};

int main(int argc,  char *argv[])
{
    pid_t pid;  //进程号
    key_t key;  //密钥key
    int semid; //信号量集标识符
    union semun my_sem_union;    //联合体,目的是初始化信号量的值
    struct sembuf sop;  //信号量结构体数组,semop函数使用
    int ret;

    key = ftok("./my_keyfile", 24678);    //获取key值
    if(key == -1) {
        printf("ftok error\n");
        exit(-1);
    } else {
        printf("ftok ok,the key is %#X\n",key);
    }

    /*  向系统申请创建信号量,参数1:密钥key
        参数2:信号量个数      参数3:权限   */
    semid = semget(key, 1, 0777 | IPC_CREAT);
    if(semid == -1) {
        printf("semget error\n");
        exit(-1);
    } else {
        printf("semid is %d\n", semid);
    }

    //初始化信号量
    my_sem_union.val = 0;   //信号量值为0
    ret = semctl(semid,0,SETVAL,my_sem_union);    //初始化第0个信号量的值
    if(ret == -1) {
        printf("semctl error\n");
        exit(-1);
    }

    pid = fork();   //创建子进程
    if(pid == -1) {
        printf("fork failed\n");
        exit(-1);
    }

    //父进程
    if(pid > 0) {
        int status; //子进程结束状态
        sop.sem_num = 0;    //信号量编号为0
        sop.sem_op = -1;    //分配资源,-1操作
        sop.sem_flg = 0;     //阻塞操作
        ret = semop(semid, &sop, 1);    //参数3:1个信号量集
        if(ret == -1) {
            printf("semop failed\n");
            exit(-1);
        }
        printf("I am father\n");
        wait(&status);  //监视子进程,等待退出
    }

    //子进程
    if(pid == 0) {
        sleep(2);   //休眠2秒,让父进程先运行
        sop.sem_num = 0;    //信号量编号为0
        sop.sem_op = 1;    //分配资源,-1操作
        sop.sem_flg = 0;     //阻塞操作
        ret = semop(semid, &sop, 1);    //参数3:1个信号量集
        printf("I am son\n");
    }

    exit(0);
}

实验现象:
Linux应用程序开发:进程的一些事儿文章来源地址https://www.toymoban.com/news/detail-423818.html

到了这里,关于Linux应用程序开发:进程的一些事儿的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux设备模型统一:桥接硬件多样性与应用程序开发的关键

    在Linux的宏大世界中,各种各样的硬件设备如星辰般繁多。从常见的USB设备到复杂的网络接口卡,从嵌入式设备到强大的服务器,Linux需要在这些差异极大的硬件上运行。这就引出了一个问题:Linux是如何统一这些不同硬件的设备模型的呢?本文将探讨Linux是如何针对不同的硬

    2024年04月10日
    浏览(45)
  • Electron打包的桌面应用程序,运行中程序黑屏、白屏等渲染进程崩溃问题解决

    使用 Electron 打包 web 项目为桌面应用程序,由于运行环境的硬件条件或其他一些原因,导致程序运行过程中黑屏、白屏、崩溃。 2.1 依赖版本: electron : v8.2.1 electron-log : v4.1.1 electron-packager : v14.2.1 2.2 运行环境 win7 x86 2.3 分析过程 2.3.1 排除 程序本身运行不会“白屏”,但运行

    2024年02月07日
    浏览(54)
  • C# WPF应用使用visual studio的安装程序类的一些坑

    否则会出现命名空间System.Configuration不存在Install的报错   var s = Context.Parameters[\\\"assemblypath\\\"].ToString() 这个里面是当前文件的路径,所以需要删除掉文件的名字才能获取目录路径 var dir = s.Substring(0, s.LastIndexOf(\\\"\\\\\\\") + 1)

    2024年02月12日
    浏览(39)
  • Building AI-Copilot:构建 LLM 支持的生成应用程序的一些经验教训和模式

    我们正在构建一个用于产品策略和生成创意的实验性人工智能副驾驶,名为“Boba”。一路上,我们学到了一些关于如何构建此类应用程序的有用经验,我们已经根据模式制定了这些应用程序。这些模式允许应用程序帮助用户更有效地与大语言模型 (LLM) 交互,编排提示以获得

    2024年02月14日
    浏览(45)
  • CoreDX DDS应用开发指南(5)开发发布应用程序

            创建发布应用程序的步骤如下: 创建或获取应用程序数据的DDL文件。 使用DDL编译器编译DDL文件。类型特定的支持和DataWriter是编译DDL的结果。 编写发布应用程序 编译发布应用程序         启用DDS的应用程序本质上是以数据为中心data-centric的。为了使这些以数

    2024年02月08日
    浏览(42)
  • Flutter中的Web应用程序开发:构建现代Web应用程序

    作者:禅与计算机程序设计艺术 作为人工智能专家,程序员和软件架构师,CTO,我今天将为大家分享有关 Flutter 中 Web 应用程序开发的见解。在这篇文章中,我们将深入探讨 Flutter Web 应用程序的开发过程、技术原理以及最佳实践。 引言 随着移动设备的普及,Web 应用程序在全

    2024年02月12日
    浏览(78)
  • Linux和windows进程同步与线程同步那些事儿(五):Linux下进程同步

    Linux和windows进程同步与线程同步那些事儿(一) Linux和windows进程同步与线程同步那些事儿(二): windows线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(三): Linux线程同步详解示例 Linux和windows进程同步与线程同步那些事儿(四):windows 下进程同步 Linux和wi

    2024年02月02日
    浏览(40)
  • 桌面应用程序开发攻略(初步了解)

            桌面应用开发 是指为桌面计算机或其他类似设备(如服务器)开发软件应用程序的过程。桌面应用通常是独立于浏览器运行的,并且可以在操作系统的桌面或应用程序菜单中找到。桌面应用可以使用各种编程语言开发,包括C++、Java、C#和Python等。桌面应用的开发

    2024年02月09日
    浏览(49)
  • 《Qt开发》MDI应用程序

    实现多个子窗体的自定义布局(自定义子窗体尺寸和位置)、平铺布局(titleSubWindows)和分页模式(QMdi::TabbedView)。 运行效果图 初始布局(自定义布局) 平铺布局 多页模式 实现过程 1. 创建项目MdiFirstDemo,并创建3个子窗体,分别为FirstSubWindow、SecondSubWindow和ThirdSubWindow。

    2024年01月16日
    浏览(51)
  • 开发GPT的应用程序流程

    开发一个基于GPT的应用程序(GPT APP)涉及到与GPT模型的集成,用户界面设计,以及应用程序的功能实现。以下是一个一般的开发流程,希望对大家有所帮助。北京木奇移动技术有限公司,专业的软件外包开发公司,欢迎交流合作。 明确目标和用例: 定义您的GPT应用的目标和

    2024年01月19日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包