进程之间的通信通过内核空间实现
IPC技术
①管道(匿名管道/命名管道-FIFO队列) ②System V IPC(消息队列、信号量和共享内存) ③套接字(UNIX套接字&Internet套接字)
※信号
软中断,信号提供了一种处理异步事件的方法,作为进程通信的一种机制,由一个进程发送给另一个进程。<signal.h>
信号的产生情况
①用户在终端按下一个组合键;
②硬件异常; //①②硬件问题
③进程调用 kill 函数发送信号;
④当检测到某软件产生异常时产生信号; //③④软件问题
信号处理
阻塞信号/捕获信号/忽略信号/执行默认动作
【SIGKILL&SIGSTOP 信号是无法捕捉和忽略的】
查看信号 kill -l / trap -l;
信号操作的函数
(1) int kill (pid_t pid,int sig); //向指定进程发送信号
头文件:<sys/types.h> <sysnal.h>
pid : ①>0; 发送指定pid的进程
②=0;信号发送给和目前进程在同一个进程组的所有进程
③=-1;广播到系统内所有进程
④<0; 发送信号给PID为pid绝对值的进程
(2) int alarm(int second); //定时器发送信号SIGALRM,默认处理是终止当前进程;
头文件:<unistd.h>
函数的返回值是0/设定闹钟还余下的秒数
(3) int raise(int sig); //发送信号给当前的进程
头文件:<signal.h>
sig参数主要是信号参数
执行成功返回0,失败返回-1;
等价于 kill (getpid(),sig);
(4) void signal(*signal(it signum,void(*handler)(int)))(int);
头文件:#include <signal.h>
signal()会按照signum指定的信号编号来设置信号的处理函数。当指定的信号到达就会处理*handler指定的函数执行;若该函数不在,则需要是以下的两个常数之一:
SIG_IGN 忽略参数signum指定的信号
SIG_DFL 将参数signum指定的信号重设为 核心预设的信号处理方式
(5) 信号集操作函数--#include <signal.h>
int sigemptyset(sigset_t *set); //清空信号集 成功返回0 错误返回-1
int sigfillset(sigset_t *set); //初始化信号集 成功返回0 失败返回-1
int sigaddset(sigset_t *set,int signo); //将signo信号加入到信号集set 成功返回0 失败返回-1
int sigdelset(sigset_t *set,int signo); //将指定信号从信号集中添加或者删除
int sigismember(const sigset_t *set,int signo);
//判断指定信号是否包含在信号集中 (不)包含返回1(0) 失败返回-1
int sigprocmask(int how,const sigset_t *set,sigset_t *old) //查询/设置信号掩码
//how参数: --成功返回0,失败返回-1
//SIG_BLOCK: 新的信号掩码由目前的信号掩码和set指定的信号掩码的并集
//SIG_UNBLOCK:将目前的信号掩码删除set指定的信号掩码
//SIG_SETMASK: 目前的信号掩码设置成set指定的信号掩码
信号发送例:设计一个程序,要求用户进程创建一个子进程,父进程向子进程发出SIGKILL信号,子进程收到此信号,结束子进程的运行;
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t sonpid;
int ret;
sonpid=fork(); //fork()函数执行一次,返回两次;在父进程中,fork返回新建进程ID;在子
//进程中fork返回0,错误返回负值;
int newret;
if(sonpid<0)
{
perror("创建进程失败!");
exit(1); //异常结束
}
else if(sonpid==0)
{
raise(SIGSTOP); //如果是子进程,发送一个不能被阻塞、处理或阻塞的暂停信号;
exit(0); //正常结束
}
else
{
printf("子进程的进程号是%d\n",sonpid);
if((waitpid(sonpid,NULL,WNOHANG))==0)
{
if(ret=kill(sonpid,SIGKILL)==0)
{
printf("用kill函数返回值是:%d,发出的SIGKILL信号结>束的进程进程号:%d\n",ret,sonpid);
}
else
{
perror("kill函数结束子进程失败");
}
}
}
}
信号处理例:要求程序运行后进入无限循环,当用户按下中断键(Ctrl+C)时,进入程序的自定义信号处理函数,当用户再次按下中断键(Ctrl+C)后,结束程序运行;
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
void fun_ctrl_c(); //自定义信号函数
int main()
{
(void)signal(SIGINT,fun_ctrl_c);
printf("主程序:主程序进入一个循环...\n");
while(1)
{
printf("这是一个无限的循环(退出请按‘Ctrl+C’)\n");
sleep(3);
}
exit(0);
}
void fun_ctrl_c()
{
printf("\t你按了Ctrl+C!\n");
printf("\t此例不处理,重新恢复SIGINT信号的系统默认处理\n");
(void) signal(SIGINT,SIG_DFL); //重新恢复SIGINT的系统默认处理
}
信号阻塞例1:
要求主程序运行时,即使按下Ctrl+C也不影响正在运行的程序,即让信号处于阻塞状态,当主体程序运行完毕后才进入自定义信号处理函数;
#include <stdlib.h>
#include <stdio.h>
#include <signal.h>
#include <sys/stat.h>
#include <unistd.h>
void fun_ctrl_c();//自定义信号函数
int main()
{
int i;
sigset_t set,pendset; //定义了两个信号集
struct sigaction action;
(void) signal(SIGINT,fun_ctrl_c);
if(sigemptyset(&set)<0) //初始化set信号集
{
perror("初始化集合错误!\n");
}
if(sigaddset(&set,SIGINT)<0) //将SIGINT信号加入到set信号集
{
perror("加入信号集错误\n");
}
if(sigprocmask(SIG_BLOCK,&set,NULL)<0) //将当前的信号集合加入到当前进程的
//阻塞集合中
{
perror("往信号阻塞集中增加一个信号集合错误");
}
else //将当前信号集加入到阻塞集合中
{
for(int i=0;i<5;i++)
{
printf("此文字表示程序在阻塞状态\n");
sleep(2);
}
}
if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0) //将当前的阻塞集中删除一个信号集
{
perror("从信号阻塞集删除一个信号集合错误");
}
}
void fun_ctrl_c() //自定义信号函数
{
printf("\t你按了Ctrl+C但是系统未处理a... ");//要求中断键不影响当前程序运行
printf("\t信号处理函数:要处理的东西在处理函数中编程!\n");
printf("\t这个案例不处理,直接退出!\n");
(void) signal(SIGINT,SIG_DFL); //恢复默认SIGINT信号的系统默认处理
}
过程:①初始化set信号集; ②将SIGINT信号加入到set信号集; ③将set信号集加入到阻塞集;
... ④将set信号集从阻塞集删除 ->5次循环 程序结束 SIGINT执行默认系统处理 直接退出系统;
信号阻塞例2:
信号SIGINT(Ctrl+C)和SIGTSTP(Ctrl+Z)是可以阻塞的,信号SIGQUIT(Ctrl+\)是不可以阻塞;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
void fun_ctrl_c();
void fun_ctrl_z();
void fun_ctrl_d();
int main()
{
int i;
sigset_t set,pendset;
struct sigaction action;
(void) signal (SIGINT,fun_ctrl_c);
(void) signal (SIGTSTP,fun_ctrl_z);
(void) signal (SIGQUIT,fun_ctrl_d);
if(sigemptyset(&set)<0) //初始化set信号集
{
perror("初始化信号集错误\n");
}
if(sigaddset(&set,SIGTSTP)<0) //将SIGTSTP信号加入到set
{
perror("Ctrl+Z加入信号集错误\n");
}
if(sigaddset(&set,SIGINT)<0) //将SIGINT信号加入到set
{
perror("Ctrl+C加入信号集错误\n");
}
if(sigprocmask(SIG_BLOCK,&set,NULL)<0) //信号集加入到当前进程的阻塞集合
{
perror("加入阻塞集合失败\n");
}
else
{
printf("加入到阻塞集合成功\n");
for(i=0;i<10;i++)
{
printf("Ctrl+C和Ctrl+Z信号处于阻塞,Ctrl+'\'信号未被阻塞\n");
sleep(3);
}
}
if(sigprocmask(SIG_UNBLOCK,&set,NULL)<0) //将当前信号集从阻塞信号集中删除
{
perror("从阻塞信号集中删除当前信号集失败\n");
}
}
void fun_ctrl_c() //自定义信号
{
int n;
printf("\t你已经按了Ctrl+C 系统未处理...");
for(n=0;n<4;n++)
{
printf("\t正在处理Ctrl+C信号处理函数");
}
}
void fun_ctrl_z() //自定义信号
{
int n;
printf("\t你已经按了Ctrl+Z 系统未处理...");
for(n=0;n<6;n++)
{
printf("\t正在处理Ctrl+Z信号处理函数");
}
}
void fun_ctrl_d()
{
int n ;
printf("\t你已经按了Ctrl+'\' 系统处理了该信号!!\n");
for(n=0;n<2;n++)
{
printf("\t正在处理Ctrl+'\'信号处理函数");
}
}
※管道
无名管道pipe & FIFO管道(命名管道),都是通过内核缓冲区实现数据的传输;
pipe用于父进程和子进程之间的通信,通过pipe()系统调用创建并打开;
FIFO在磁盘上有对应的结点,但是有数据块,通过mknod()系统调用或mkfifo()函数来建立;一旦建立,任何进程都可以通过文件名将其打开进行读写;
管道实质是一个内核缓冲区,以先进先出的方式从缓冲区写读数据;
无名管道
建立管道用pipe函数,管道操作:
①父进程用pipe开辟管道,得到的两个文件描述符指向管道的两端;
②父进程用fork创建子进程,子进程也有两个文件描述符指向管道两端;、
③父(子)进程关闭读(写) 端,就可以进行写(读)操作;--读read函数 / 写write函数
(1)pipe函数--#include <unistd.h>
int pipe(int filedes[2]);
filedes[0]管道读取端; filedes[1]管道写入端; 成功执行返回0,错误返回-1;
(2)memset函数--#include<string.h>
void *memset(void *s,int c,size_t n);
s指向的内存区域内前n个字节以参数c填入,返回指向s的指针;c虽然声明是int,但是必须是unsigned char,范围在0-255;
例:要求创建一个管道,复制进程【创建子进程】,父进程往管道中写入字符串,子进程从管道中读取前字符串;
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
int main()
{
pid_t result; //子进程返回的进程号
int r_num;
int pipe_fd[2]; //两个文件描述符
char buf_r[100],buf_w[100]; //读写字符数组
memset(buf_r,0,sizeof(buf_r)); //初始化数组设置为0
//
if(pipe(pipe_fd)<0)
{
perror("创建管道失败\n");
return -1;
}
result = fork();//创建子进程,复制进程
if(result<0)
{
perror("创建子进程失败\n");
exit(-1);
}
else if(result==0) //子进程
{
close(pipe_fd[1]); //关闭写
if((r_num=read(pipe_fd[0],buf_r,100))>0) //进行读
{
printf("子进程从管道中读取%d个字符,读取的字符内容是:%s\n",r_num,buf_r);
}
close(pipe_fd[0]); //关闭读
exit(0);//正常退出
}
else //父进程
{
close(pipe_fd[0]); //关闭读
printf("请从键盘输入要写入管道的字符串\n");
scanf("%s",buf_w);
if(write(pipe_fd[1],buf_w,strlen(buf_w))!=-1) //进行写
{
printf("父进程向管道写入:%s\n",buf_w);
}
close(pipe_fd[1]); //关闭写
waitpid(result,NULL,0); //waitpid,阻塞父进程,等待子进程退出;
exit(0);
}
}
注意:空字符不读取;
命名管道
命名管道的名字对应磁盘的索引节点,用该文件名,任何进程都有相应的权限对其进行访问。
创建命名管道的方式:mkfifo()和mknode()函数
例:设计两个程,要求用命名管道FIFO实现简单的聊天功能。
高级管道设计
例:设计一个程序,要求用popen创建管道,实现“ls -l|grep 7-9c”的功能;
//高级管道设计
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
int main()
{
FILE *fp; //文件指针
int num;
char buf[5000]; //字符缓冲区
memset(buf,0,sizeof(buf)); //将buf所指向的内存区域的前sizeof(buf)得到>的字节
//设置为0,初始化清空的操作
printf("建立管道...\n");
fp=popen("ls -l","r"); //调用popen函数,建立读管道
if(fp!=NULL)
{
num=fread(buf,sizeof(char),5000,fp);
/*
if(num>0)
{
printf("第一个命令是'ls-l',执行结果如下:\n");
printf("%s\n",buf);
}
*/
if(num<0)
{
perror("读命令失败!\n");
exit(-1);
}
pclose(fp);
}
else
{
printf("用popen创建管道失败!\n");
return 1;
}
fp=popen("grep insert.c","w"); //建立写管道
printf("第二个命令是grep insert.c,运行结果是:\n");
fprintf(fp,"%s\n",buf);
pclose(fp);
return 0;
}