| 副标题[/!--empirenews.page--]   学习在 Linux 中进程是如何与其他进程进行同步的。 本篇是 Linux 下进程间通信(IPC)系列的第一篇文章。这个系列将使用 C 语言代码示例来阐明以下 IPC 机制: 
    共享文件共享内存(使用信号量)管道(命名的或非命名的管道)消息队列套接字信号 在聚焦上面提到的共享文件和共享内存这两个机制之前,这篇文章将带你回顾一些核心的概念。 核心概念进程是运行着的程序,每个进程都有着它自己的地址空间,这些空间由进程被允许访问的内存地址组成。进程有一个或多个执行线程,而线程是一系列执行指令的集合:单线程进程就只有一个线程,而多线程的进程则有多个线程。一个进程中的线程共享各种资源,特别是地址空间。另外,一个进程中的线程可以直接通过共享内存来进行通信,尽管某些现代语言(例如 Go)鼓励一种更有序的方式,例如使用线程安全的通道。当然对于不同的进程,默认情况下,它们不能共享内存。 有多种方法启动之后要进行通信的进程,下面所举的例子中主要使用了下面的两种方法: 
    一个终端被用来启动一个进程,另外一个不同的终端被用来启动另一个。在一个进程(父进程)中调用系统函数 fork,以此生发另一个进程(子进程)。 第一个例子采用了上面使用终端的方法。这些代码示例的 ZIP 压缩包可以从我的网站下载到。 共享文件程序员对文件访问应该都已经很熟识了,包括许多坑(不存在的文件、文件权限损坏等等),这些问题困扰着程序对文件的使用。尽管如此,共享文件可能是最为基础的 IPC 机制了。考虑一下下面这样一个相对简单的例子,其中一个进程(生产者 producer)创建和写入一个文件,然后另一个进程(消费者consumer)从这个相同的文件中进行读取:           writes +-----------+ readsproducer-------->| disk file |<-------consumer                 +-----------+
 在使用这个 IPC 机制时最明显的挑战是竞争条件可能会发生:生产者和消费者可能恰好在同一时间访问该文件,从而使得输出结果不确定。为了避免竞争条件的发生,该文件在处于读或写状态时必须以某种方式处于被锁状态,从而阻止在写操作执行时和其他操作的冲突。在标准系统库中与锁相关的 API 可以被总结如下: 
    生产者应该在写入文件时获得一个文件的排斥锁。一个排斥锁最多被一个进程所拥有。这样就可以排除掉竞争条件的发生,因为在锁被释放之前没有其他的进程可以访问这个文件。消费者应该在从文件中读取内容时得到至少一个共享锁。多个读取者可以同时保有一个共享锁,但是没有写入者可以获取到文件内容,甚至在当只有一个读取者保有一个共享锁时。 共享锁可以提升效率。假如一个进程只是读入一个文件的内容,而不去改变它的内容,就没有什么原因阻止其他进程来做同样的事。但如果需要写入内容,则很显然需要文件有排斥锁。 标准的 I/O 库中包含一个名为 fcntl的实用函数,它可以被用来检查或者操作一个文件上的排斥锁和共享锁。该函数通过一个文件描述符(一个在进程中的非负整数值)来标记一个文件(在不同的进程中不同的文件描述符可能标记同一个物理文件)。对于文件的锁定, Linux 提供了名为flock的库函数,它是fcntl的一个精简包装。第一个例子中使用fcntl函数来暴露这些 API 细节。 示例 1. 生产者程序#include <stdio.h>#include <stdlib.h>#include <fcntl.h>#include <unistd.h> #define FileName "data.dat" void report_and_exit(const char* msg) {  [perror][4](msg);  [exit][5](-1); /* EXIT_FAILURE */} int main() {  struct flock lock;  lock.l_type = F_WRLCK;    /* read/write (exclusive) lock */  lock.l_whence = SEEK_SET; /* base for seek offsets */  lock.l_start = 0;         /* 1st byte in file */  lock.l_len = 0;           /* 0 here means 'until EOF' */  lock.l_pid = getpid();    /* process id */   int fd; /* file descriptor to identify a file within a process */  if ((fd = open(FileName, O_RDONLY)) < 0)  /* -1 signals an error */    report_and_exit("open to read failed...");   /* If the file is write-locked, we can't continue. */  fcntl(fd, F_GETLK, &lock); /* sets lock.l_type to F_UNLCK if no write lock */  if (lock.l_type != F_UNLCK)    report_and_exit("file is still write locked...");   lock.l_type = F_RDLCK; /* prevents any writing during the reading */  if (fcntl(fd, F_SETLK, &lock) < 0)    report_and_exit("can't get a read-only lock...");   /* Read the bytes (they happen to be ASCII codes) one at a time. */  int c; /* buffer for read bytes */  while (read(fd, &c, 1) > 0)    /* 0 signals EOF */    write(STDOUT_FILENO, &c, 1); /* write one byte to the standard output */   /* Release the lock explicitly. */  lock.l_type = F_UNLCK;  if (fcntl(fd, F_SETLK, &lock) < 0)    report_and_exit("explicit unlocking failed...");   close(fd);  return 0;}
 上面生产者程序的主要步骤可以总结如下: |