系统编程之进程间通信

一、进程间通信简述 进程是程序的一次运行的动态过程,为了完成一个任务,很多进程之间需要进行通信,从而相互合作以实现需要的功能。操作系统内核中提供了进程间通信的方法,主要有以下几种: 管道: 管道是最基本的进程通信机制,可以想象成一个管道,两端分别连着 2 个进程,一个进程往里面写,一个进程从里面读。如果读或写管道的时候没有内容可供读或写,进程将被阻塞,直到有内容可供读写为止。 消息队列: 消息队列本质上在内核空间中开辟了一块内存空间,这块内存是其他进程可以访问到的,在其中使用链表的方式实现了一个队列,进程可以向该队列中发送数据块或读取数据块,从而达到进程间通信的目的。其中每个数据块包含两部分,首先是一个类型为 long 的 type,然后是具体的数据,数据块的 type 可以作为进程之间相互约定好的协议。例如一个进程发送 type 为123的消息,另一个进程接收 type 为123 的消息,后者便可确认这就是前者发送的信息,并信任该数据块中的数据。 信号量: 不同进程之间存在对资源的竞争,信号量就是用来标明可用资源的数量的数据结构,本质是为了实现多个进程之间的同步。需要注意,信号量(semaphore)与 “信号”(signal)没有关系。 共享内存: 共享内存的本质就是把两个或多个进程的虚拟地址映射到同一块物理内存。这样,一个进程通过对这块内存的读写就能被其他进程访问到,从而实现进程间通信的功能。 二、进程间通信操作 (1)准备操作 获取 key #include <sys/types.h> #include <sys/ipc.h> key_t ftok( const char * fname, int id ); 共享内存,消息队列,信号量等进程间通信方式都需要寻找一个中间介质来进行通信。不同的介质需要用不同的信息来进行区分,这就是进程间通信的 key。ftok() 函数就可以生成一个唯一的 key,该函数获取一个文件路径和一个字序号,生成一个用于区分的 key。 注意,选择文件路径只是因为文件的编号是独有的。设置的文件路径与代码和程序并没有什么关系。 命令管理进程间通信 若没有调用控制函数进行删除,则已分配的进程间通信不会自动释放。如果共享内存,消息队列,信号量在新进程执行时依旧有之前残留的信息,可能导致程序运行结果错误。可以通过 ipcs 和 ipcrm 命令进行管理。 ipcs # 显示所有进程间通信信息 ipcrm -q MsgID # 删除消息队列 ipcrm -s SemID # 删除信号量 ipcrm -m ShrID # 删除共享内存 (2)消息队列 消息队列获取 #include <sys/types....

十一月 16, 2022 · 5 分钟 · 962 字 · Wokron

对不重复随机数的数学分析

一、引子——双色球 我们知道,双色球每注投注号码由 6 个红色球号码和 1 个蓝色球号码组成。红色球号码从 1—33 中选择;蓝色球号码从 1—16 中选择。现在要求写出一个程序,模拟双色球的抽奖过程。 我们很容易想到使用某种方法生成一定范围内的随机数。蓝色球很好解决,但对于红色球,需要的是随机生成 6 个号码不同的数,可一定范围内的随机数总可能出现相同的情况,这样要如何解决? 也就是说:对于 $1, 2, ..., m$ 这 m 个数字,随机抽取其中 $n(n \lt m)$ 个数。要采取怎样的算法? 二、两种思路 其一 : 暴力方法 的确,随机数总有可能出现相同的情况,但是我们知道,同一个数多次出现的概率很小,以至于我们可以将其忽略。因此,我们只需要不断地取范围内的随机数,遇到重复的舍弃,直到取得的数字数目达到 n 即可。 void rand1(int m, int n, int rands[]) { char* hasSelect = calloc(m+1, sizeof(char)); for (int i = 0; i < n; ) { int r = randomInt(1, m); if (!hasSelect[r]) { rands[i++] = r; hasSelect[r] = 1; } } free(hasSelect); } 但这种算法是有缺陷的。问题在于,概率达到多小才算作可以忽略? 考虑 $m=100, n=99$ 的情况。首先,取得第一个球,一定只需要选取 1 次;但是,我们再计算一下取得最后一个球的选取次数。为了取得第 99 个数,选中的概率为 $p_{99} = \frac{1}{50}$。我们设离散型随机变量 $X$ 表示为了取得第 99 个数所需的选取次数,则 $P\{X = k\} = (1-p)^{k-1}p$ 服从几何分布。因此 $E(X) = \frac{1}{p} = 50$。从期望上看,需要整整 50 次才能取到第 99 个数!!这中间的差距说明了,在 m 一定时,随着 n 的增大,随机的效率明显降低。...

十一月 13, 2022 · 3 分钟 · 569 字 · Wokron

系统编程之信号及信号处理

一、信号简介 (1)信号含义 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件。在软件层次上是对中断机制的一种模拟;在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是进程间通信机制中唯一的异步通信机制,一个进程不必通过任何操作来等待信号的到达,事实上,进程也不知道信号到底什么时候到达。进程之间可以互相通过系统调用 kill 发送软中断信号。内核也可以因为内部事件而给进程发送信号,通知进程发生了某个事件。信号机制除了基本通知功能外,还可以传递附加信息。 (2)信号分类 可以使用kill -l命令查看当前系统支持的所有信号: 信号值小于 SIGRTMIN(<=34)的信号都是不可靠信号。它的主要问题是信号可能丢失。 信号值位于 SIGRTMIN 和 SIGRTMAX 之间的信号都是可靠信号,这些信号支持排队,不会丢失。 (3)信号的产生 信号可以由一下几种方式产生: 键盘事件:ctrl+c ctrl+\ ctrl+Z 等 非法内存:如果内存管理出错,系统就会发送一个信号进行处理 硬件检测到异常:如段错误,除 0,总线错误等 环境切换:比如说从用户态切换到其他态,状态的改变也会发送一个信号,这个信号会告知给系统 系统调用:如调用kill,raise,sigsend ,sigqueue函数等 (4)信号处理 进程可以通过三种方式响应信号: 接受默认处理 忽略信号(某些信号不能被忽略,如 SIGKILL 和 SIGSTOP) 捕捉信号并执行信号处理程序 二、信号操作 (1)信号发送 系统调用中用于发送信号的函数有 kill() raise() abort() 等。 kill() 函数 #include <signal.h> int kill(pid_t pid, int sig); //第一个参数pid代表接受信号的进程PID,第二个参数代表要发送的信号 参数 pid 会影响 kill()函数的作用,取值分为以下四种情况 若 pid>0,则发送信号 sig 给进程号为 pid 的进程。 若 pid=0,则发送信号 sig 给当前进程所属进程组的所有进程。 若 pid=-1,则发送信号 sig 给除 1 号进程和当前进程外的所有进程。 若 pid<-1,则发送信号 sig 给属于进程组 pid 的所有进程。 segqueue() 函数 sigqueue()函数支持发送信号的同时传递参数,需要配合 sigaction() 函数一起使用。...

十一月 13, 2022 · 3 分钟 · 584 字 · Wokron

系统编程之进程管理

一、引言 进程是操作系统中的重要概念,是对执行一定功能的程序的过程的抽象。这篇文章将简要说明进程的相关知识。介绍进程管理相关的函数,并通过这些函数实现重定向和进程间通信等功能。 二、进程简介 1. 程序执行原理 程序在编译后以二进制方式存在于外存上,执行的时候被操作系统载入内存。以 Linux 系统上的 C 语言编译出来的程序为例,载入的过程简单来说就是把编译完成的 ELF (Executable and Linkable Format 可执行与可链接格式) 文件的几个段的内容读取到内存指定位置,然后初始化寄存器的内容,将指令寄存器(比如cs:ip)指向程序入口,再初始化一些进程相关内容就完成了。 在某一次时钟中断发生的时候,进程主动陷入内核态,进行进程切换的系统调用,CPU 将切换到另一个进程工作。总而言之,整个计算机从开机到关机,就是一个不断创建、切换、终止进程的过程。 2. 进程概念的用途 早期的计算机一次只能执行一个程序,这种程序完全控制系统,并且访问所有系统资源。相比之下,现代计算机系统允许“同时”加载多个应用程序到内存,以便并发(轮流)执行。 这种改进要求对各种程序提供更严的控制和更好的划分。这些需求导致了进程概念的诞生。 进程是现代分时操作系统的工作单元,是操作系统向运行中的程序进行资源分配的单位。进程包括程序代码(文本),当前活动(程序计数器,寄存器的值),堆栈,数据端,堆。 需要注意区分程序和进程的概念。程序是被动实体,如存储在磁盘上的可执行文件;进程是活动实体,具有一个程序计数器用于表示下个执行命令和一组相关资源。 当一个可执行文件被加载到内存时,这个程序就成为进程。 两个进程可以与同一程序相关联,但当作两个单独的执行序列,虽然文本段相同,但是数据、堆、堆栈不同。 三. 进程管理 接下来介绍使用操作系统 API 进行进程管理的方法。 1. 使用 fork 创建新进程 #include <unistd.h> pid_t fork(); fork 无参数,返回一个用于指示子进程的 pid(对于子进程,返回值为 0)。其作用是创建一个子进程,共享父进程所有内容,并且这个子进程会接着 fork 下面的代码继续执行。fork有以下两种用法: 一个父进程希望复制自己,使父进程和子进程同时执行不同的代码段。 一个进程要执行一个不同的程序。在这种情况下,子进程从fork返回后立即调用exec。 如果在调用 fork 后子进程先于父进程结束,则子进程就会变为僵尸进程,虽然结束,却依然占据了进程表中的一个位置。为了避免这种情况,需要调用 wait 或 waitpid 来使父进程等待子进程结束,并释放子进程的信息。 #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid,int *status,int options); 下面将以一个程序作为例子。该程序由父进程创建两个子进程,父进程打印字符 B ,两个子进程分别打印 A 和 C ,并且要使最终的输出为 ABC 。...

十月 31, 2022 · 4 分钟 · 651 字 · Wokron

系统编程之命令行编译

一、前言 本文将简单介绍在Linux系统下的命令行编译流程。介绍gcc、gdb、make等工具的简单使用。 二、GCC 基本操作 编译选项 无选项编译链接 用法:gcc test.c 作用:将 test.c 预处理、编译、汇编并链接形成可执行文件。这里未指定输出文件,默认输出为 a.out。 选项 -o 用法:gcc test.c -o test 作用:将 test.c 预处理、编译、汇编并链接形成可执行文件 test。-o 选项用来指定输出文件的文件名。 选项 -E 用法:gcc -E test.c -o test.i 作用:将 test.c 预处理输出 test.i 文件。 选项 -S 用法:gcc -S test.i 作用:将预处理输出文件 test.i 编译成 test.s 文件。 选项 -c 用法:gcc -c test.s 作用:将汇编语言文件 test.s 汇编成目标代码 test.o 文件。 无选项链接 用法:gcc test.o -o test 作用:将目标代码文件 test.o 链接成最终可执行文件 test。 选项 -O 用法:gcc -O1 test.c -o test 作用:使用编译优化级别 1 编译程序。级别为 1~3,级别越大优化效果越好,但编译时间越长。 官方文档:GCC, the GNU Compiler Collection...

九月 12, 2022 · 3 分钟 · 589 字 · Wokron