进程通信 - d11

回顾:

进程的退出方式 - 主函数执行完毕,遇到return语句
执行了 exit() 或 _Exit() 函数
用信号强行退出进程
wait()和waitpid()函数 - 让父进程等待子进程结束,区别在于wait()等待任意一个子进程的结束;waitpid()可以选择等待的子进程,也可以选择不等待,直接返回。
vfork()+execl() 方式创建进程,子进程不会复制父进程的任何资源,而是直接使用父进程的资源,导致父进程阻塞,直到调用execl()函数为止。换而言之:execl()会让父子进程并行。

信号 - Unix系统的软件中断的方式。自定义信号处理的代码结构:

#include <signal.h>      //信号专用的头文件
void fa(int signo){ .... }  //信号处理函数
int main(){
   ...;
   signal(SIGXXX,fa); //设置信号处理方式
   ...;
}

今天:
   信号 - 父子进程的信号处理方式继承性、信号发送函数、
          信号集和信号屏蔽、信号的相关函数(sleep()/usleep())、计时器
   IPC  - 进程间通信(两个或多个进程间的数据交互)
alternate text
父子进程之间的信号处理方式:
    fork()创建的子进程对于信号的处理方式 完全继承 父进程的信号处理方式(与父进程的信号处理方式一样)。
    vfork()+execl()创建的子进程,部分继承父进程的信号处理方式。父进程是默认或忽略的,子进程会继承下来;
    父进程是自定义处理函数,子进程会改为默认处理(因为子进程中没有 父进程处理函数的代码的)。

fork()之前的代码只有父进程执行一次,fork()之后的代码 父子进程分别执行一次(执行两次)。
信号的发送方式
1 键盘发送信号(部分)
      ctrl+c  -> 信号2
      ctrl+\  -> 信号3

2 硬件故障发送信号(部分)
      内存没有映射 -> 段错误
      硬盘上的文件映射出错 -> 总线错误

3 kill 命令发送信号 (全部)
      kill -信号值  进程PID

4 信号发送函数(程序员的方式)
      raise() alarm() kill() sigqueue() ...
 讲 kill()和alarm(),严格来说,alarm()不算一个信号发送函数
int kill(pid_t pid,int signo)
//  函数功能: 给进程发送信号
//  参数: pid 代表某个或某些进程,使用方式和waitpid()一样
//                  >0  代表某个进程
//                  -1  代表给所有 有发送权限的进程发送信号
//                  0   代表给本组进程发送信号
//                  <-1 代表给进程组ID是-pid的进程发送信号
//         signo 就是发送信号的值
//  成功返回0,失败返回 -1 。
//  注: 可以用 0 信号 测试 是否有发送信号的权限,而不会有任何附带的问题。

/*
sleep() 和 usleep()
功能都是休眠,区别在于 sleep() 以秒作为单位,usleep()以微秒为单位。
sleep()的结束可能是时间到了,也可能是被 未忽略的信号打断,此时将返回休眠的剩余秒数。
/*
 信号的产生和到来时间都是无法控制的。因此在执行一些关键代码时,信号有可能带来负面影响。
 解决方案:使用信号屏蔽技术。信号屏蔽 不是 阻止 信号的到来,而是 暂时忽略信号的处理,直到解除信号屏蔽后再 处理信号。

 信号屏蔽 需要使用 信号集,信号集 就是 信号的集合。不是所有信号都可以被屏蔽,比如信号 9 。
 long long int -> 64位整数
 信号集 可以看出一个 超大的整数。类型是:sigset_t  。一个数据结构包括:逻辑结构、物理结构、运算结构。通常的运算结构包括:
 创建、销毁、增加、删除、修改、查询,其他功能。其中,修改不是必须的,先删除后增加 也可以实现修改的效果。
 信号集提供了以下功能函数:
     sigaddset()  - 增加信号
     sigdelset()  - 删除信号
     sigfillset()  - 增加所有信号
     sigemptyset() - 删除所有信号
     sigismember() - 查询某个信号是否存在

在信号集中,倒数第n位代表 信号n,权重是 2的n-1次方。

信号屏蔽 由 sigprocmask() 实现,解除信号屏蔽也使用这个函数。
int sigprocmask(int how,sigset_t* new,sigset_t* old)
// 函数功能: 屏蔽信号 或者 解除信号屏蔽
// 参数: how 是屏蔽的算法,包括三个值,实际上使用SIG_SETMASK即可。就是 直接使用 new作为新的屏蔽
//        new 新的信号屏蔽集
//        old 屏蔽之前的 信号屏蔽集 ,用于 解除屏蔽,如果不需要考虑解除屏蔽,old 用 NULL 即可。
// 返回 0 代表成功,-1 代表失败。
sigaction() 是 signal()的增强版,拥有更多的功能。
 计时器 - 一段时间以后开始运行某个功能,每隔一段时间再次运行
 相对于alarm(),计时器精确到 微秒。(纯了解内容)
 计时器的原理就是 一段时间后 每隔一段时间 产生一个信号,然后在信号处理时执行 相应的代码。计时器包括:真实、虚拟和实用三种,一般使用的是 真实计时器。

 计时器函数: setitimer()设置计时器 、getitimer()获取当前计时器
 setitimer(ITIMER_REAL,&timer,0);

进程间通信 - IPC

常见的IPC包括:
      文件
      信号 (signal)
      管道
      共享内存
      消息队列
      信号量集(semaphore arrays)
      网络编程socket
  ........

 其中,共享内存、消息队列和信号量集 遵循相同的规范,因此统称为XSI IPC。
 IPC 的原理都是一样的,都是通过 媒介进行数据交互,这个媒介基本上是 内存或者硬盘上的文件。

 管道( pipe )
    管道的交互媒介是 一种特殊的文件,叫 管道文件。
    管道 分为 有名管道和无名管道,无名管道 只能用于 fork()创建的父子进程之间IPC,有名管道没有限制。
    命令 touch 、函数open() 等 都不能创建出管道文件,管道文件的创建 必须使用 mkfifo命令 或 mkfifo() 函数。但 读写管道文件的函数和 普通文件 一样。

    如果只对管道文件进行读操作 或者 写操作,会 卡住(阻塞),数据无法交互。必须 同时 具备 读操作和写操作 ,数据 才能正常传输,传输完成后,文件中 不会存留 数据。
    管道是最古老的IPC方式之一,但 目前较少使用。
    mkfifo b   创建管道文件
    ls > b
    另外一个终端 : cat b

练习&作业:
     用管道文件实现IPC,从pipea.c 发送100个 int 到 pipeb.c。和读写普通文件的方式一样。

example

/*
* /alarm.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void fa(int signo){
  printf("时间到了,该起床了\n");
	//exit(0);
	alarm(1);
}
int main(){
	signal(SIGALRM,fa);
	alarm(3);//3秒后发送信号SIGALRM
	//alarm(0);//取消闹钟
  while(1);
}

/*
 * fork.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void fa(int signo){
  printf("捕获了信号%d\n",signo);
}
int main(){
  signal(SIGINT,fa);
	signal(SIGQUIT,SIG_IGN);
	//printf("begin\n");
  pid_t pid = fork();
	//printf("end\n");
	if(pid==0){
		//signal(SIGINT,SIG_DFL);
	  printf("cpid=%d\n",getpid());
		while(1);
	}
	printf("father over\n");
}//练习: 测试vfork()+execl() 信号继承效果

/*
 * itimer.c
 */

#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/time.h>

void fa(int signo){
  printf("I am superman!\n");
}
int main(){
  signal(SIGALRM,fa);
  struct itimerval timer;
	timer.it_interval.tv_sec = 1;//间隔秒数
	timer.it_interval.tv_usec = 100000;//微秒数
	timer.it_value.tv_sec = 5;//开始秒数
	timer.it_value.tv_usec = 0;
	setitimer(ITIMER_REAL,&timer,0);
	while(1);
}

/*
 * kill.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void fa(int signo){
  printf("捕获了信号%d\n",signo);
}
int main(){
  pid_t pid = fork();
  if(pid == 0){
		signal(SIGINT,fa);
		sleep(10);
	  printf("child over\n");
	  exit(0);
	}
	sleep(1);
  kill(pid,SIGINT);
	printf("father over\n");
}

/*
 * proc.c
 */

int main(){
  while(1);
}
/*
 * sigprocmask.c
 */
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

void fa(int signo){
  printf("捕获了信号%d\n",signo);
}
int main(){
	printf("pid=%d\n",getpid());
  signal(SIGINT,fa); signal(50,fa);
  printf("执行普通代码,没有屏蔽信号\n");
	sleep(15);
  printf("执行关键代码,开始屏蔽信号\n");
  sigset_t set,old; sigemptyset(&set); 
	sigaddset(&set,2);sigaddset(&set,50);
  int res=sigprocmask(SIG_SETMASK,&set,&old);
	if(res==-1) perror("sigmask"),exit(-1);
  sleep(15);
	/*sigset_t pend;
	sigpending(&pend);
  if(sigismember(&pend,2))
		printf("信号2来过\n");*/
  printf("关键代码结束,解除信号屏蔽\n");
	sigprocmask(SIG_SETMASK,&old,NULL);
	sleep(15);
}

/*
 * sigset.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

int main(){
  sigset_t set;
	printf("size=%d\n",sizeof(set));
	printf("%d\n",set);//有警告
  sigemptyset(&set);printf("%d\n",set);//0
	sigaddset(&set,2);printf("%d\n",set);//+2
	sigaddset(&set,3);printf("%d\n",set);//+4
	sigaddset(&set,7);printf("%d\n",set);//+64
  sigdelset(&set,3);printf("%d\n",set);
  if(sigismember(&set,2) == 1)
	  printf("信号2存在\n");
	else printf("信号2不存在\n");
	return 0;
}

/*
 * sleep.c
 */

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void fa(int signo){
  printf("捕获了信号%d\n",signo);
}
int main(){
	signal(SIGINT,fa);
  printf("开始休眠\n");
	int res = sleep(10);
	if(res == 0) printf("睡的好\n");
	else printf("被打断了,剩余%d秒\n",res);
	usleep(10000);//0.01秒
	printf("很快\n");
}

/*
 * vfork.c
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

void fa(int signo){
  printf("捕获了信号%d\n",signo);
}
int main(){
  signal(SIGINT,fa);
	signal(SIGQUIT,SIG_IGN);
  pid_t pid = vfork();
	if(pid==0){
	  printf("cpid=%d\n",getpid());
		execl("./proc","proc",NULL);
	}
  printf("father over\n");
}

all:
	gcc -m32 -o alarm       alarm.c
	gcc -m32 -o fork        fork.c
	gcc -m32 -o itimer      itimer.c
	gcc -m32 -o kill        kill.c
	gcc -m32 -o proc        proc.c
	gcc -m32 -o sigprocmask sigprocmask.c
	gcc -m32 -o sigset      sigset.c
	gcc -m32 -o sleep       sleep.c
	gcc -m32 -o vfork       vfork.c

clean:
	rm -rf  alarm       \
		fork        \
		itimer      \
		kill        \
		proc        \
		sigprocmask \
		sigset      \
		sleep       \
		vfork       \