Interprocess communication - 1 data stream

当进程间可以共享数据, 并且可以等待其他进程是否执行完毕时, 程序将变得更加强大.

显然共享数据和等待都是内部进程通信的范畴, 这里主要讲一下 data stream.

每个进程都有一个descriptor table, 这个表包含了file descriptor(a number) 和 data stream的映射关系. 如图 : 

 A file descriptor is a number that represents a data stream.

默认会有3条映射关系, 0, 1 , 2 分别代表stdin, stdout, stderr.

    这里的映射关系可以通过dup2 系统函数来修改, 例如要把Database connection 这个data stream放到1对应的data stream上. 执行 dup2(3,1) .

    当使用open打开一个文件的时候, 会在descriptor table里面寻找有没有空的slot, 找到数值最小的空闲slot, 然后在这个file descriptor上建立一条映射. 这个descriptor table最大可以存储255条映射.

    在命令行中使用> <进行重定向时 , 例如 : 

      1>? 表示标准输出重定向到?,    

      2>?表示标准错误重定向到?.  

      还可以使用&1,&2. 

           例如2>? 1>&2 表示标准错误重定向到?, 并且标准输出重定向到标准错误.

[root@db-172-16-3-150 ~]# ls -al 1>./ls.log 2>&1
[root@db-172-16-3-150 ~]# cat ls.log
total 15216
drwxr-x--- 16 root     root         4096 Aug 20 08:15 .
drwxr-xr-x 31 root     root         4096 Jun 14 12:42 ..
-rw-------  1 root     root         1812 Oct 21  2011 anaconda-ks.cfg
-rw-------  1 root     root        25321 Aug 19 21:32 .bash_history
-rw-r--r--  1 root     root           24 Jan  6  2007 .bash_logout
-rw-r--r--  1 root     root          224 Dec 30  2011 .bash_profile
-rw-r--r--  1 root     root          176 Jan  6  2007 .bashrc

    接下来这个程序使用fopen创建一个文件数据流, 并使用dup2将这个数据流放置到 file descriptor= 1 (也就是stdout) 的条目上. 然后往stdout和这个FILE data stream分别写入一条消息. 看看是不是都会写入同一个文件.

[root@db-172-16-3-150 zzz]# cat c.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>

// 打印错误消息的函数
void error(char * msg) {
  fprintf(stderr, "%s: %s\n", msg, strerror(errno));
  exit(1);
}

int main() {
  // 新建一个FILE data stream.
  FILE * my_file = fopen("/tmp/my_file.log", "a+");
  if ( my_file == NULL ) {
    error("open my_file error.");
  }
  // fileno()获取file descriptor.
  int fd = fileno(my_file);
  // 使用dup2 将my_file的 file descriptor对应的data stream 拷贝到1对应的data stream位置. 替换原来的1对应的stdin
  if( dup2(fd, 1) == -1 ) {
    error("dup2 fd to 1 error");
  }
  // 分别打印到stdout 和 my_file data stream.
  fprintf(stdout, "this message will write to /tmp/my_file.log by stdout data stream.\n");
  fprintf(my_file, "this message will write to /tmp/my_file.log by my_file FILE data stream.\n");
  // 同步数据到硬盘.
  if ( fdatasync(fd) == -1 ) {
    error("fdatasync(fd) error");
  }
  // 关闭my_file数据流.
  if ( fclose(my_file) == EOF ) {
    error("fclose(my_file) error");
  }
  return 0;
}

编译 : 

[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c

执行 : 

[root@db-172-16-3-150 zzz]# ./c
[root@db-172-16-3-150 zzz]# cat /tmp/my_file.log
this message will write to /tmp/my_file.log by my_file FILE data stream.
this message will write to /tmp/my_file.log by stdout data stream.

使用fprintf写入 stdout和my_file 都写到了/tmp/my_file.log 

下面穿插一个 fork 和 waitpid 的使用场景.

为什么主进程要等待子进程执行完毕呢? 

来看个例子 : 

[root@db-172-16-3-150 zzz]# cat c.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
// 要fork的子进程个数
#define MAX_PROCESS 200

// 错误输出函数
void error(char * msg) {
  fprintf(stderr, "%s: %s\n", msg, strerror(errno));
  exit(1);
}

int main() {
  // 创建一个FILE data stream
  FILE * my_file = fopen("/tmp/my_file.log", "a+");
  if ( my_file == NULL ) {
    error("open my_file error.");
  }
  int fd = fileno(my_file);
  if( dup2(fd, 1) == -1 ) {
    error("dup2 fd to 1 error");
  }
  pid_t p[MAX_PROCESS];
  int i;
  for (i=0; i<MAX_PROCESS; i++) {
    p[i] = fork();
    // 子进程执行以下
    if ( p[i] == 0 ) {
      // sleep(100) 模拟子进程执行过程要100秒.
      sleep(100);
      fprintf(stdout, "%i, this message will write to /tmp/my_file.log by stdout data stream.\n", i);
      fprintf(my_file, "%i, this message will write to /tmp/my_file.log by my_file FILE data stream.\n", i);
      return 0;
    }
  }
  if ( fdatasync(fd) == -1 ) {
    error("fdatasync(fd) error");
  }
  if ( fclose(my_file) == EOF ) {
    error("fclose(my_file) error");
  }
  return 0;
}

执行 : 

[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c
[root@db-172-16-3-150 zzz]# ./c
[root@db-172-16-3-150 zzz]# cat /tmp/my_file.log
[root@db-172-16-3-150 zzz]# 

没有任何内容, 因为子进程还在执行, 并没有往/tmp/my_file.log写入任何信息.

等待100秒后再次查看这个文件的内容就有了.

像这种场景, 如果主进程等待子进程执行结束则不会发生误解. 

修改代码如下 : 

[root@db-172-16-3-150 zzz]# cat c.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

#define MAX_PROCESS 200

void error(char * msg) {
  fprintf(stderr, "%s: %s\n", msg, strerror(errno));
  exit(1);
}

int main() {
  FILE * my_file = fopen("/tmp/my_file.log", "a+");
  if ( my_file == NULL ) {
    error("open my_file error.");
  }
  int fd = fileno(my_file);
  if( dup2(fd, 1) == -1 ) {
    error("dup2 fd to 1 error");
  }
  pid_t p[MAX_PROCESS];
  // 存储子进程返回的状态, 注意这不仅仅包含返回值, 还有其他信息. 只是用了int类型来存储, 其实前8个bit位存储的才是返回值 .
  // 推荐使用WEXITSTATUS()来获取这个返回值.
  int p_stat[MAX_PROCESS];
  int i;
  for (i=0; i<MAX_PROCESS; i++) {
    p[i] = fork();
    if ( p[i] == 0 ) {
      sleep(100);
      fprintf(stdout, "%i, this message will write to /tmp/my_file.log by stdout data stream.\n", i);
      fprintf(my_file, "%i, this message will write to /tmp/my_file.log by my_file FILE data stream.\n", i);
      return 0;
    }
  }
  // 主进程将等待所有子进程结束. 才继续.
  for (i=0; i<MAX_PROCESS; i++) {
    if ( waitpid(p[i], &p_stat[i], 0) == -1 ) {
      error("waitpid error");
    }
    if (WIFEXITED(p_stat[i])) {
      printf("exited, status=%d\n", WEXITSTATUS(p_stat[i]));
    } else if (WIFSIGNALED(p_stat[i])) {
      printf("killed by signal %d\n", WTERMSIG(p_stat[i]));
    } else if (WIFSTOPPED(p_stat[i])) {
      printf("stopped by signal %d\n", WSTOPSIG(p_stat[i]));
    }
  }
  //
  if ( fdatasync(fd) == -1 ) {
    error("fdatasync(fd) error");
  }
  if ( fclose(my_file) == EOF ) {
    error("fclose(my_file) error");
  }
  return 0;
}

如果waitpid等待的进程号不是子进程的进程号会怎么样呢?

[root@db-172-16-3-150 zzz]# cat c.c
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

// 错误输出函数
void error(char * msg) {
  fprintf(stderr, "%s: %s\n", msg, strerror(errno));
  exit(1);
}

int main() {
  int p_stat;
  waitpid((pid_t) 1, &p_stat, 0);
  if (WIFEXITED(p_stat)) {
    printf("exited, status=%d\n", WEXITSTATUS(p_stat));
  } else if (WIFSIGNALED(p_stat)) {
    printf("killed by signal %d\n", WTERMSIG(p_stat));
  } else if (WIFSTOPPED(p_stat)) {
    printf("stopped by signal %d\n", WSTOPSIG(p_stat));
  }
  return 0;
}
结果 :
[root@db-172-16-3-150 zzz]# gcc -O3 -Wall -Wextra -Werror -g ./c.c -o c && ./c
killed by signal 52

【参考】

dup, dup2 : 

NAME
       dup, dup2 - duplicate a file descriptor

SYNOPSIS
       #include <unistd.h>

       int dup(int oldfd);
       int dup2(int oldfd, int newfd);

DESCRIPTION
       dup() and dup2() create a copy of the file descriptor oldfd.

       After  a  successful return from dup() or dup2(), the old and new file descriptors may be used interchangeably.
       They refer to the same open file description (see open(2)) and thus share file offset and  file  status  flags;
       for  example,  if  the  file offset is modified by using lseek(2) on one of the descriptors, the offset is also
       changed for the other.

       The two descriptors do not share file descriptor  flags  (the  close-on-exec  flag).   The  close-on-exec  flag
       (FD_CLOEXEC; see fcntl(2)) for the duplicate descriptor is off.

       dup() uses the lowest-numbered unused descriptor for the new descriptor.

       dup2() makes newfd be the copy of oldfd, closing newfd first if necessary.

RETURN VALUE
       dup()  and  dup2() return the new descriptor, or -1 if an error occurred (in which case, errno is set appropri-
       ately).

wait, waitpid

NAME
       wait, waitpid - wait for process to change state

SYNOPSIS
       #include <sys/types.h>
       #include <sys/wait.h>

       pid_t wait(int *status);
       pid_t waitpid(pid_t pid, int *status, int options);
       int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

DESCRIPTION
       All  of  these  system  calls  are used to wait for state changes in a child of the calling process, and obtain
       information about the child whose state has changed.  A state change is considered to be: the child terminated;
       the  child  was  stopped by a signal; or the child was resumed by a signal.  In the case of a terminated child,
       performing a wait allows the system to release the resources associated with the child; if a wait is  not  per-
       formed, then terminated the child remains in a "zombie" state (see NOTES below).

       If a child has already changed state, then these calls return immediately.  Otherwise they block until either a
       child changes state or a signal handler interrupts the call (assuming that system calls are  not  automatically
       restarted  using  the SA_RESTART flag of sigaction(2)).  In the remainder of this page, a child whose state has
       changed and which has not yet been waited upon by one of these system calls is termed waitable.

fopen

NAME
       fopen, fdopen, freopen - stream open functions

SYNOPSIS
       #include <stdio.h>

       FILE *fopen(const char *path, const char *mode);
       FILE *fdopen(int fildes, const char *mode);
       FILE *freopen(const char *path, const char *mode, FILE *stream);

DESCRIPTION
       The  fopen()  function  opens the file whose name is the string pointed to by path and associates a stream with
       it.

       The argument mode points to a string beginning with one of the following sequences (Additional  characters  may
       follow these sequences.):

       r      Open text file for reading.  The stream is positioned at the beginning of the file.

       r+     Open for reading and writing.  The stream is positioned at the beginning of the file.

       w      Truncate file to zero length or create text file for writing.  The stream is positioned at the beginning
              of the file.

       w+     Open for reading and writing.  The file is created if it does not exist, otherwise it is truncated.  The
              stream is positioned at the beginning of the file.

       a      Open  for  appending (writing at end of file).  The file is created if it does not exist.  The stream is
              positioned at the end of the file.

       a+     Open for reading and appending (writing at end of file).  The file is created if it does not exist.  The
              initial  file position for reading is at the beginning of the file, but output is always appended to the
              end of the file.

fclose

NAME
       fclose - close a stream

SYNOPSIS
       #include <stdio.h>

       int fclose(FILE *fp);

DESCRIPTION
       The fclose() function will flush the stream pointed to by fp (writing any buffered output data using fflush(3))
       and close the underlying file descriptor.

RETURN VALUE
       Upon successful completion 0 is returned.  Otherwise, EOF is returned and the global variable errno is  set  to
       indicate  the  error.   In  either  case  any further access (including another call to fclose()) to the stream
       results in undefined behaviour.

fsync, fdatasync

NAME
       fsync, fdatasync - synchronize a file’s in-core state with storage device

SYNOPSIS
       #include <unistd.h>

       int fsync(int fd);

       int fdatasync(int fd);

DESCRIPTION
       fsync()  transfers  ("flushes")  all  modified in-core data of (i.e., modified buffer cache pages for) the file
       referred to by the file descriptor fd to the disk device (or other permanent storage device)  where  that  file
       resides.   The call blocks until the device reports that the transfer has completed.  It also flushes  metadata
       information associated with the file (see stat(2)).

       Calling fsync() does not necessarily ensure that the entry in  the  directory  containing  the  file  has  also
       reached disk.  For that an explicit fsync() on a file descriptor for the directory is also needed.

       fdatasync() is similar to fsync(), but does not flush modified metadata unless that metadata is needed in order
       to allow a subsequent data retrieval to be correctly handled.  For example, changes  to  st_atime  or  st_mtime
       (respectively,  time  of  last  access  and time of last modification; see stat(2)) do not not require flushing
       because they are not necessary for a subsequent data read to be handled correctly.  On the other hand, a change
       to the file size (st_size, as made by say ftruncate(2)), would require a metadata flush.

       The aim of fdatasync(2) is to reduce disk activity for applications that do not require all metadata to be syn-
       chronised with the disk.

exit

NAME
       _exit, _Exit - terminate the current process

SYNOPSIS
       #include <unistd.h>

       void _exit(int status);

       #include <stdlib.h>

       void _Exit(int status);

DESCRIPTION
       The  function  _exit() terminates the calling process "immediately". Any open file descriptors belonging to the
       process are closed; any children of the process are inherited by process 1, init, and the process’s  parent  is
       sent a SIGCHLD signal.

       The value status is returned to the parent process as the process’s exit status, and can be collected using one
       of the wait() family of calls.

       The function _Exit() is equivalent to _exit().

RETURN VALUE
       These functions do not return.

fileno

NAME
       clearerr, feof, ferror, fileno - check and reset stream status

SYNOPSIS
       #include <stdio.h>

       void clearerr(FILE *stream);
       int feof(FILE *stream);
       int ferror(FILE *stream);
       int fileno(FILE *stream);

DESCRIPTION
       The function clearerr() clears the end-of-file and error indicators for the stream pointed to by stream.

       The  function feof() tests the end-of-file indicator for the stream pointed to by stream, returning non-zero if
       it is set.  The end-of-file indicator can only be cleared by the function clearerr().

       The function ferror() tests the error indicator for the stream pointed to by stream, returning non-zero  if  it
       is set.  The error indicator can only be reset by the clearerr() function.

       The function fileno() examines the argument stream and returns its integer descriptor.

       For non-locking counterparts, see unlocked_stdio(3).
时间: 2024-09-13 19:15:26

Interprocess communication - 1 data stream的相关文章

linux 之进程间通信-------------InterProcess Communication

进程间通信至少可以通过传送打开文件来实现,不同的进程通过一个或多个文件来传递信息,事实上,在很多应用系统里,都使用了这种方法.但一般说来,进程间 通信(IPC:InterProcess Communication)不包括这种似乎比较低级的通信方法.Unix系统中实现进程间通信的方法很多,而且不幸的是,极少方法能在所有的Unix系 统中进行移植(唯一一种是半双工的管道,这也是最原始的一种通信方式).而Linux作为一种新兴的操作系统,几乎支持所有的Unix下常用的进程间通信 方法:管道.消息队列.

c#进程间通信(Inter-Process Communication)

原文:c#进程间通信(Inter-Process Communication) c#进程间通信(IPC, Inter-Process Communication) 接收端: using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using S

[LeetCode] Find Median from Data Stream

Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value. Examples: [2,3,4] , the median is 3 [2,3], the median is (2 + 3) / 2 = 2.5 Design a d

Interprocess communication - 2 signal handler

内核通过信号来控制程序. 程序接收到信号后, 停止所有事务, 调用handler funciton来响应对应的信号. 如图 :    进程有自己的signal mappings table :    正因为有了signal mappings table, 所以可以创建自己的handler function , 响应各种信号. 1. 首先要创建handler函数. 2. 然后创建一个sigaction结构体, 记录handler function, mask(在执行handler function

高可用之4——Data Guard 配置Standby Redo Log

转载:http://blog.itpub.net/35489/viewspace-672422 Data Guard在最大保护和最高可用性模式下,Standby数据库必须配置standby redo log,通过下面的实 验展示创建的原则和过程. 1.原则 1).standby redo log的文件大小与primary 数据库online redo log 文件大小相同 2).standby redo log日志文件组的个数依照下面的原则进行计算    Standby redo log组数公式

Serial Communication Protocol Design Hints And Reference

前面转载的几篇文章详细介绍了UART.RS-232和RS-485的相关内容,可以知道,串口通信的双方在硬件层面需要约定如波特率.数据位.校验位和停止位等属性,才可以正常收发数据.实际项目中使用串口通信时,一般还需要设计一套通讯协议.那么设计串口通讯协议有什么需要注意的地方呢?本文以iPod.车载CAN解码盒及IoT设备EnOcean的串口通讯协议为例,对通讯协议数据帧的定义做一个简单的介绍和分析. 首先看Apple提供的文档<iPod_Accessory_Protocol_Interface_S

学习Java的笔记(5)

笔记   121.Stream根据功能可分为数据侦听器流(data Sink Stream,侦听器:内存.管道.文件)和进程流 (Processing Stream)122.在谈Java的流类之前,先谈如何生成一个File对象,它时一个和流无关的类.File对象可用来生成 和文件(及其所在的路径)或目录结构相关的对象,由于不同的系统可能会有不同的目录结果表示法,使 用File可完成和系统无关的目的(使用抽象的路径表示法).123.File对象生成方法: File(String path):将一个

iOS 开发库(iOS Developer Library)

iOS 开发库(iOS Developer Library) 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 以下为详尽部分,如感觉过于冗长,可移步<iOS 开发库概要(iOS Devel

读懂Java中的Socket编程(转)

Socket,又称为套接字,Socket是计算机网络通信的基本的技术之一.如今大多数基于网络的软件,如浏览器,即时通讯工具甚至是P2P下载都是基于Socket实现的.本文会介绍一下基于TCP/IP的Socket编程,并且如何写一个客户端/服务器程序.  餐前甜点  Unix的输入输出(IO)系统遵循Open-Read-Write-Close这样的操作范本.当一个用户进程进行IO操作之前,它需要调用Open来指定并获取待操作文件或设备读取或写入的权限.一旦IO操作对象被打开,那么这个用户进程可以对