1.pipe管道通信
int pipe (int __pipedes[2]);
pipefd读写文件描述符,0:代表读, 1:代表写
返回值:失败返回-1,成功返回0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
int main(int argc, char* argv[])
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if (pid == 0) //son
{
sleep(3);
char buf[] = "hello word\n";
write(fd[1], buf, sizeof(buf));
}
else if (pid > 0)//father
{
char buf2[100] = {0};
int ret = 0;
ret = read(fd[0], buf2, sizeof(buf2));// 阻塞
if (ret > 0)
{
write(STDOUT_FILENO, buf2, sizeof(buf2));
}
}
exit(0);
}
lee@lenovo:~/code/unix$ ./app
hello word
父子进程实现pipe通信,实现ps aux|grep bash功能
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
int main(int argc, char* argv[])
{
int fd[2];
int ret = 0;
pipe(fd);
pid_t pid = fork();
if (pid == 0) //son
{
// 关闭pipe读端
close(fd[0]);
// 1.重定向
ret = dup2(fd[1], STDOUT_FILENO); // STDOUT_FILENO 重定向到 fd[1]
if (-1 == ret)
{
perror("dup2");
exit(1);
}
// 2.execlp
ret = execlp("ps", "ps", "aux", NULL);
if (-1 == ret)
{
perror("execlp");
exit(1);
}
}
else if (pid > 0)//father
{
// 关闭pipe写端
close(fd[1]);
// 1.重定向 标准输入重定向像到管道读端
dup2(fd[0], STDIN_FILENO);
// 2.execlp
execlp("grep", "grep", "bash", "--color=auto", NULL);
}
exit(0);
}
读管道:
写端全部关闭:read读到0, 相当于读到文件末尾
写端没有全部关闭:
有数据:read读到数据
没有数据:read阻塞 fcntl函数可以更改非阻塞
写管道:
读端全部关闭:产生一个信号SIGPIPE,程序异常终止
读端没有全部关闭:
管道已满:write阻塞
管道没有满:write正常写入
int main(int argc, char* argv[])
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if (0 == pid) //son
{
sleep(3);
close(fd[0]); // 关闭读端
char buf[] = "hello word\n";
write(fd[1], buf, sizeof(buf));
close(fd[1]); // 关闭写端
while (1)
{
sleep(1);
}
}
else if (0 < pid)//father
{
char buf2[100] = {0};
int ret = 0;
close(fd[1]); // 关闭写端
while (1)
{
ret = read(fd[0], buf2, sizeof(buf2));// 阻塞
if (0 < ret)
{
write(STDOUT_FILENO, buf2, sizeof(buf2));
}
else if (0 == ret)
{
printf("read over\n");
break;
}
}
}
exit(0);
}
写端全部关闭:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char* argv[])
{
int fd[2];
pipe(fd);
pid_t pid = fork();
if (0 == pid) //son
{
sleep(3);
close(fd[0]); // 关闭读端
char buf[] = "hello word\n";
write(fd[1], buf, sizeof(buf));
close(fd[1]); // 关闭写端
while (1)
{
sleep(1);
}
}
else if (0 < pid)//father
{
close(fd[0]); // 关闭读端
close(fd[1]); // 关闭写端
int status = 0;
wait(&status);
if (WIFSIGNALED(status))
{
printf("killed by %d\n", WTERMSIG(status));
}
}
exit(0);
}
读端全部关闭,写端如果写入会产生SIGPIPE信号,程序异常终止:
管道大小:8*512byte
int CheckPIPESize(void)
{
int fd[2];
pipe(fd);
long size = 0;
size = fpathconf(fd[0], _PC_PIPE_BUF);
printf("fd[0] pipe size: %d\n", size);
size = fpathconf(fd[1], _PC_PIPE_BUF);
printf("fd[1] pipe size: %d\n", size);
return 0;
}
管道的优劣:
优点:简单,相比信号、套接字实现进程间通信,简单很多;
缺点:1.只能单向通信,双向通信需要建立两个管道;
2.只能用于父子、兄弟进程(有共同祖先)间通信,该问题后来使用fifo有名管道解决;
兄弟进程通信
int main(int argc, char* argv[])
{
int fd[2];
int ret = 0;
pipe(fd);
int n = 2;
pid_t pid = 0;
size_t i = 0;
for (i = 0; i < n; i++)
{
pid = fork();
if (0 > pid)
{
perror("fork");
exit(1);
}
else if (0 == pid)
{
// printf("son i = %d, pid = %d, ppid = %d\n", i, getpid(), getppid());
break;
}
// else if (0 < pid)
// {
// printf("father i = %d, pid = %d, ppid = %d\n", i, getpid(), getppid());
// }
}
if (2 == i)
{
printf("[父] father pid: %d, son pid: %d\n", getppid(), getpid());
// 关闭pipe读端
close(fd[0]);
// 关闭pipe写端
close(fd[1]);
wait(NULL);
}
else if (0 == i)
{
sleep(3);
printf("[兄弟发送] father pid: %d, son pid: %d\n", getppid(), getpid());
// 关闭pipe读端
close(fd[0]);
// 1.重定向
ret = dup2(fd[1], STDOUT_FILENO); // STDOUT_FILENO 重定向到 fd[1]
if (-1 == ret)
{
perror("dup2");
exit(1);
}
// 2.execlp
ret = execlp("ps", "ps", "aux", NULL);
if (-1 == ret)
{
perror("execlp");
exit(1);
}
}
else if (1 == i)
{
printf("[兄弟接收] father pid: %d, son pid: %d\n", getppid(), getpid());
// 关闭pipe写端
close(fd[1]);
// 1.重定向 标准输入重定向像到管道读端
dup2(fd[0], STDIN_FILENO);
// 2.execlp
execlp("grep", "grep", "bash", "--color=auto", NULL);
}
exit(0);
}
2.FIFO通信
FIFO有名管道,实现无血缘关系进程通信;
创建一个管道的伪文件
mkfifo FIFO 命令创建
也可以用函数 int mkfifo(const char* pathname, mode_t mode);
内核会针对fifo文件开辟一个缓冲区,操作fifo文件,可以操作缓冲区,实现进程间通信---实际上就是文件读写
open注意事项:打开fifo文件的时候,read端会阻塞等待write端open,write端同理,也会阻塞等待另外一端打开;
man 7 fifo 查看详细信息
mkfifo FIFO
3.mmap共享映射区
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
addr: 传NULL
length: 映射区的长度
prot:
PROT_EXEC Pages may be executed. 可执行
PROT_READ Pages may be read. 可读
PROT_WRITE Pages may be written. 可写
PROT_NONE Pages may not be accessed. 不可访问
flags:
MAP_SHARED: 共享的,对内存的修改会影响到源文件
MAP_PRIVATE: 私有的
fd:文件描述符,open打开一个文件
offset: 偏移量
返回值:
成功返回 可用的内存首地址
失败返回 MAP_FAILED
释放映射区:
int munmap(void *addr, size_t length);
addr 传mmap地址
length: mmap创建的长度
返回值:成功返回0 失败返回-1
int main(int argc, char* argv[])
{
int fd = open("./file", O_RDWR);
// 创建映射区
char *mem = mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if (mem == MAP_FAILED)
{
perror("mmap");
exit(1);
}
// 拷贝数据
strncpy(mem, "hello\n", 6);
// 释放mmap
munmap(mem, 8);
close(fd);
exit(0);
}
int main(int argc, char* argv[])
{
int fd = open("./file", O_RDWR);
// 创建映射区
// char *mem = mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
char *mem = mmap(NULL, 8, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
if (mem == MAP_FAILED)
{
perror("mmap");
exit(1);
}
// 拷贝数据
strncpy(mem, "world\n", 6);
// 释放mmap
munmap(mem, 8);
close(fd);
exit(0);
}
关于mmap的9个问题:
1.如果更改mem变量的地址,释放的时候munmap,传入mem还能成功吗?
不能
2.如果对mem越界操作会怎么样?
文件的大小对映射区操作区有影响,尽量避免
3.如果文件偏移量随便填一个数会怎么样?
offset必须是4k的整数倍
4.如果文件描述符先关闭,对mmap映射有没有影响?
没有影响
5.open的时候,可以新创建一个文件来创建映射区吗
不可以用大小为0的文件
6.open文件选择O_WRONLY,可以吗?
不可以,没有权限(隐含一次读操作)
7.当选择MAP_SHARED的时候,open文件选择O_RDONLY,prot可以选择PROT_READ|PROT_WRITE吗?
不可以,SHARED的时候,映射区的权限<=open文件的权限
8.mmap什么情况下会报错?
很多情况
9.如果不判断返回值会怎么样?
必须判断
匿名映射:
MAP_ANON, ANONYMOUS这两个宏在有些unix系统没有
/dev/zero 聚宝盆,可以随意映射
/dev/null 无底洞,一般错误信息重定向到这个文件中
用mmap支持无血缘关系进程通信
如果进程要通信,flags必须设为MAP_SHARED
4.信号
信号的特点:简单,不能带戴昂信息,满足特定条件发生
信号的机制:进程B发送给进程A,内核产生信,内核处理
信号的产生:
按键产生 ctrl+c ctrl+z ctrl+\
调用函数 kill raise abort
定时器 alarm, setitimer
命令产生kill
硬件异常 段错误,浮点型错误,总线错误,SIGPIPE
信号的状态:
产生
递达 信号到达并且处理完
未决 信号被阻塞了
信号的默认处理方式:
忽略
执行默认动作
捕获
信号的4要素:
编号
事件
名称
默认处理动作
忽略
终止
终止+core
暂停
继续