系统编程--进程间通信

四种方式

  • 管道(仅有血缘关系的使用)
  • 信号 (开销小)
  • 套接字
  • 共享映射区

一、管道

linux下文件类型共七种:
普通,目录,软链接
字符,块,管道,套接字-----伪文件,不占用磁盘,仅占用内存缓冲区。

管道需要保证单向流通,不能有第三方连通管道


1.1 管道的特点

  • 由两个文件描述符引用,一个表示读,一个表示写
  • 规定数据从写端流向读端
  • 由环形队列实现,借助内存缓冲区(大小4k),因此读取后就消失
  • 无论匿名还是有名,读取之后数据就会消失

弊端

  • 不能自己读自己写
  • 每个数据只能读取一次
  • 半双工通信,单向流动
  • 只能在有公公祖先的进程使用管道

1.2 PIPE()

int pipe(int pipefd[2])
//参数fd[0]读端     fd[1]写端
//成功返回0,失败-1

例子

int fd[2];
pipe(fd);
pid_t pid=fork();
char buf[1024];
//每个进程都有读写两端连接管道
if(pid>0){
close(fd[0]);//关闭读
write(fd[1],"hello",strlen("hello"));//父亲写
close(fd[1]);

}
else{
close(fd[1]);//关闭写
ret=read(fd[0],buff,sizof(buff));//儿子读
}

1.3 管道读写行为


如果管道中无数据

  • 若写端全部关闭,read返回0;
  • 若写端未关闭,则阻塞(让出cpu)


如果读端关闭,则异常终止

如果读端未关闭

  • 管道已满,write阻塞
  • 未满,正常读写

1.4 有名管道FIFO(创建管道文件)

//命令行
mkfifo filename

#include
//库函数,man 3 mkfifo

int mkfifo(char * filename,mode_t mode);

1.4.1 实例,搭配sprintf使用

用sprintf将要写的数据存储

//1.创建fifo文件,mkfifo myfifo
//2.编写两个.c文件,相当于两个无血缘关系的进程
//fifo_w:
char buf[4096];
int fd=open("myfifo",O_WRONLY);
while(1){
sprintf(buf,"hello fifo %d\n",i++);
write(fd,buf,strlen(buf));
sleep(1);
}


//fifo_r:
char buf[4096];
int fd=open("myfifo",O_RDONLY);
while(1){
len=read(fd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,len);
sleep(1);
}


二、存储映射mmap

将磁盘映射到内存,即可以通过指针来操作文件,操作的目标还是文件所以可以无血缘通信

/*
void*指任意返回类型,用任何指针都可以

addr:一般填NULL,表示系统自己分配映射地址
length:<文件大小,否则会出总线错误
prot:共享映射区读写属性 PROT_READ、PROT_WRITE、PROT_READ | PROT_WRITE
flags:标注共享内存属性 MAP_SHARED、MAP_RIVATE
offset:偏移位置,需要是4k的整数倍
return:映射的首地址
              MAP_FAILED.errno

*/
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

案例

char * c=mmap(xxxxx);
strcpy(c,"hello");

2.1 munmap

int munmap(void *addr, size_t len);
//释放映射区 

2.2 mmap保险调用方式

  • fd=open(文件名, O_RDWR);
  • mmap(NULL,有效文件大小, PROT_READ|PROT_WRITE, MAP_SHARED, fd,0);

2.3 mmap父子间通信

        int fd=open("mmap.txt",O_CREAT|O_RDWR,0644);
        ftruncate(fd,50);
        int len=lseek(fd,0,SEEK_END);
        char * p=mmap(NULL,len,PROT_READ|PROT_WRITE, MAP_SHARED, fd,0);

         if(p==MAP_FAILED){
               perror("mmap error");
                  exit(1);
          }
          pid=fork();

          if(pid==0){
            strcpy(p,"i am child");
          }else{
            sleep(1);
            strcpy(p+strlen(p),"i am father");
            printf("%s",p);
            wait(NULL);
       }

通过读写同一个指针,来进行通信

2.3 mmap无血缘通信

//两个文件,一个读一个写,mmap_w,mmap_r
//mmap_w同上
//mmap_r:
   int fd=open("mmap.txt",O_RDONLY,0644);
   int len=lseek(fd,0,SEEK_END);
   char * p=mmap(NULL,len,PROT_READ|PROT_WRITE, MAP_SHARED, fd,0);
    printf("%s",p);

mmap内部数据可以多次读取,fifo不行

三、信号

信号产生和处理都是由内核操作
阻塞信号集:对此集合信号设置屏蔽,当屏蔽x信号之后,再收到该信号,就对该信号进行处理推后()
未决信号集:未处理的信号

3.1 信号的处理方式

  • 执行默认动作
  • 忽略
  • 捕捉(执行用户处理函数)

3.2 信号四要素

  • 名称
  • 编号
  • 信号对应事件
  • 信号默认处理动作

3.3 常规信号

  1. SIGHUP: 当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
  2. SIGINT:当用户按下了组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程
  3. SIGQUIT:当用户按下组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程
  4. SIGILL:CPU检测到基进程执行了非法指令。默认动作为终止进程并产生core文件。
  5. SIGTRAP:该信号由断点指令或其他trap指令产生。默认动作为终止里程并产生core文件。
  6. SIGABRT:调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
  7. SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core文件。
  8. SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件
  9. SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
  10. SIGUSR1:用户定义的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
  11. SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
  12. SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
  13. SIGPIPE::Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。
  14. SIGALRM:定时器超时,超时的时间由系统调用alarm设置。默认动作为终止进程。
  15. SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阳塞和终止。通常用来要示程序正常退出。执行shell命令Kll时,缺省产生这个信号。默认动作为终止进程。
  16. SIGSTKFLT: Linux 早期版本出现的信号,现仍保留向后兼容。**默认动作为终止进程。(不用)
  17. SIGCHLD:子进程状态发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。
  18. SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。
  19. SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。
  20. SIGTSTP:停止终端交互进程的运行。按下组合键时发出这个信号。默认动作为暂停进程。

3.4 alarm

设置定时器,结束后向进程发送SIGALARM,结束进程,返回0或者剩余时长,
重新调用alarm会打断当前,返回剩余时间。
因此暂停alarm使用alarm(0);

int alarm(int seconds);


3.5 信号集的操作(未决,阻塞)

3.5.1 信号集的设置

sigset_t set;     //设置信号集

int sigemptyset(sigset_t * set);    //清空信号集
int sigfillset(sigset_t * set);    //将信号集全部置1
int sigaddset(sigset_t * set,int signum);      //将某个信号加入信号集 1
int sigdelset(sigset_t *set,int signum)     //将某个信号清出信号集 0
int sigismember(const sigset_t *set, int signum);    //判断某个信号是否在信号集

3.5.2 sigprocmask

设置阻塞信号集

 int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
//
/*
how的取值
SIG_BLOCK    设置阻塞
SIG_UNBLOCK    取消阻塞
SIG_SETMASK    用自定义mask替换set

oldset:传出参数,旧有的mask
*/

3.5.3 sigpending

查看未决信号集

int sigpending(sigset_t *set);
//set 传出参数,查看未决信号集

案例

//设置ctrl c阻断
sigset_t set,oldset,tempset;
sigemptyset(&set);
sigaddset(&set,SIGINT);
int ret=sigprocmask(SIG_BLOCK,&set,&oldset);
sigpending(&tempset);
for(int i=1;i<32;i++)
  if(sigismember(set,i))
          putchar(1);
else
          putchar(0);

3.6 信号捕捉

注册一个信号捕捉函数

#include
//该操作又signal和sigaction函数
 signal(int sig, sig_t func);

 int sigaction(int sig, const struct sigaction * act, struct sigaction * oact);

3.7 信号回收子进程

3.7.1 SIGCHILD

产生条件:子进程状态发生变动,包括终止暂停等

void catch_child(int signo){
     wait(NULL);
     return;
  }
 
父进程执行:
struct sigaction act;
act.sa_handler=catch_child;
sigemptyset(&act.sa_mask);
act.sa_flags=0;
sigaction(SIGCHLD,&act,NULL);

四、本地套接字

两通信者,互相创建两个socket,通过socket连接建立通信

server.c

int lfd=socket(AF_UNIX,SOCK_STREAM / SOCK_DGRAM,0);
struct sockaddr_un addr;
/*
struct sockaddr_un{
_kernel_sa_family_t sun_family;      //AF_UNIX
char sun_path[UNIX_PATH_MAX];      //socket文件名
};
*/
strcpy(addr.sun_path,"srv.socket");
int len=offsetof(struct sockaddr_un,sun_path)+strlen("srv.socket");
unlink("srv.socket");
bind(fd,(struct sockaddr*)&addr,len);//bind函数调用成功时,会创建一个socket,为了保证创建成功,在bind之前会调用unlink
listen(lfd,28);


while(1){
socklen_t clen=sizeof(caddr);
accept(lfd,(struct sockaddr*)&caddr,&len);
}

client.c

int cfd=socket(AF_UNIX,SOCK_STREAM / SOCK_DGRAM,0);
struct sockaddr_un caddr,saddr;
/*
struct sockaddr_un{
_kernel_sa_family_t sun_family;      //AF_UNIX
char sun_path[UNIX_PATH_MAX];      //socket文件名
};
*/
strcpy(caddr.sun_path,"clie.socket");
int len=offsetof(struct sockaddr_un,sun_path)+strlen("clie.socket");
unlink("clie.socket");
bind(fd,(struct sockaddr*)&addr,len);

saddr.sun_family=AF_UNIX;
strcpy(saddr.sun_path,"srv.socket");
int len=offsetof(struct sockaddr_un,sun_path)+strlen("srv.socket");
connect(cfd,(struct sockaddr*)&saddr,len); 

你可能感兴趣的:(系统编程--进程间通信)