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