一: 管道 - 便捷
1> 本质: 内核的缓冲区(伪文件)
伪文件 - 不占用磁盘空间
2> 特点:
1) 两部分:
读端, 写端, 对应两个文件描述符
数据写端流入, 读端流出
2) 操作管道的进程被销毁之后, 管道自动被释放
3) 管道默认是阻塞的
读写都阻塞
3> 原理:
1) 内部实现方式: 队列
环形队列
特点: 先进先出
2) 缓冲区大小:
默认4k, 会适当做调整, 不会无限放大
4> 管道的局限性
1) 队列:
数据只能读一次, 不能重复读取
2) 半双工方式:
单工: 遥控器(单向)
半双工: 对讲机(可以双向传输, 但是只能以阻断方式, 同一时间只允许一方传输)
数据传输的方向是单向的
双工: 电话(可以双方同时传输)
3) 匿名管道:
适用于有血缘关系的进程
5> 创建匿名管道
1) int pipe(int pipefd[2]);
int pipefd[2] - 传出参数(out): 环形队列读端与写端的文件描述符
pipefd[0] - 读端
pipefd[1] - 写端
返回值:
success: 0
faild: -1, perror();
如果文件描述符表此时没有打开的文件, pipefd[0]=3 pipefd[1]=4;
6>设置非阻塞管道
1) 默认读写两端都阻塞
2) 设置读端为非阻塞pipe(fd[]);
1. 使用fcntl() - 变参函数
复制文件描述符 - dup
修改文件的属性 - open的时候对应的flag属性
2. 设置方法
获取原来的flags
int flags = fcntl(fd[0], F_GETFL);
设置新的flags
flags |= O_NONBLOCK;
fcntl(fd[0], F_SETFL, falgs);
二: 信号 - 系统开销小, 由内核产生并发送
1> 特点:
1) 简单
2) 携带的信息量少
3) 使用在某个特定的场景中
4) 发送进程将信号发送给内核, 再由内核发送给目标进程
5) 信号的优先级比较高, 进程收到信号之后, 暂停正在处理的事情, 优先处理信号, 处理完成之后再继续开始暂停的工作
6) 可能会造成变量数据的污染, 信号的处理逻辑影响正常执行代码块的变量值
2> 信号的状态:
1) 产生
键盘触发: ctrl+c
命令: kill命令
系统函数: kill函数
软条件: 定时器
硬条件: 段错误, 除0错误 等非法的操作
会变为未决状态
2) 未决状态 - 没有被处理的状态
转变为递达状态的行为:
1. 被忽略
2. 被捕捉
3. 执行了默认的动作
会变为递达状态
3) 递达状态 - 信号被处理了
3> 处理方式:
4> 信号的四要素:
5> 通过man文档查看信号:
1) man 7 signal
2) The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored.
SIGKILL(强制杀死) 和 SIGSTOP(强制暂停) 不能被捕捉, 阻塞 或者忽略
6> 概念: 阻塞信号集, 未决信号集:
1) 在pcb中(内核), 不能直接操作
2) 阻塞信号集:
里面存放要屏蔽的信号
3) 未决信号集: 如果信号被阻塞了, 该信号集会对阻塞的信号
没有被处理的信号的集合
7> 信号相关函数:
1) kill -- 发送信号给指定进程
函数原型: int kill(pid_t pid, int sig);
pid > 0: 发送信号给指定进程
pid = 0: 发送信号给 与调用kill函数进程属于同一进程组的所有进程
pid < -1: 取 |pid| 发给对应进程组
pid = -1: 发送给进程有权限发送的系统中所有进程
2) raise -- 自己给自己发信号
函数原型: int raise(pid_t pid, int sig);
返回值:
3) abort -- 给自己发送异常终止信号
函数原型: void abort(void);
对应信号: SIGABRT(终止进程并产生core文件)
没有参数没有返回值, 永远不会调用失败
4) 闹钟(定时器)
1. alarm -- 设置定时器(每个进程只有一个定时器)
使用的时自然定时法
不受进程状态影响
函数原型: unsigned int alarm(unsigned int seconds);
参数: 秒
当时间到达之后, 函数发出一个信号量: SIGALRM
当定时器触发时 再次调用alarm(0), 传入参数0, 此时会将定时器取消
当定时器触发时 再次调用alarm(x), 传入参数x(x > 0), 此时会将定时器时间重置为x, 返回值是之前设置定时器将要触发的剩余时间
返回值: 距离上一次设置的触发时间, 还剩余多少秒
2. setitimer -- 定时器, 并实现周期性定时 // <sys/time.h>
函数原型:
int setitimer(
int which, // 定时法则:
ITIMER_REAL(自然定时法, 到时间后发出SIGALRM信号),
ITIMER_VIRTUAL(只计算用户区代码运行的时间(区别于内核区), 到时间后发出SIGVTALRM信号),
ITIMER_PROF(计算用户+内核运行的时间, 不计算运行损耗. 到时间后发出SIGPROF信号)
const struct itimerval* new_value,
struct itimerval* old_value // 一般NULL, 传出参数, 会写出上一次定时器的信息
);
struct itimerval {
struct timeval it_interval, // 定时器循环周期
struct timeval it_value, // 第一次触发定时器的时间
};
struct timeval {
time_t tv_sec, // seconds, 秒
suseconds_t tv_usec, // microseconds, 微秒
};
基础配置:
int main(int argc, char* argv[]) {
// 设置定时器
struct itimerval new_value;
// 第一次触发的时间 tv_sec(秒) tv_usec(微秒) 同时设置 是相加的操作 周期 = 秒 + 微秒
// 其中一个不设置就赋值为0, 如若不进行赋值, 其值为随机值
new_value.it_value.tv_sec = 2; // 秒
new_value.it_value.tv_usec = 0; // 微秒
// 周期性定时
// 其中一个不设置就赋值为0, 如若不进行赋值, 其值为随机值
new_value.it_interval.tv_sec = 1; // 每n秒发一次信号
new_value.it_interval.tv_usec = 0; // 微秒
// 倒计时两秒后周期性发送信号, 需要监听该信号, 否则进程会被杀死
int ret = setitimer(ITIMER_REAL, &new_value, NULL);
while(1) {
printf("hello, itimer!\n");
sleep(1);
}
return 0;
}
8> 信号集 -- 信号集操作相关函数
1) 概念
1. 未决信号集;
没有被当前进程处理的信号
1. 阻塞信号集;
将某个信号放到阻塞信号集, 这个信号就不会被进程处理
阻塞解决之后, 信号被处理(从未决到了递达状态)
2) 自定义信号集
int sigemptyset(sigset_t* set); // 将集合置空
int sigfillset(sigset_t* set); // 将所有信号加入set集合
int sigaddset(sigset_t* set, int signo);
将signo信号加入到set集合
int sigdelset(sigset_t* set, int signo);
从set集合中移除signo信号
int sigismember(const sigset_t* set, int signo);
判断信号是否存在
3) sigprocmask函数
屏蔽and接触信号屏蔽, 将自定义信号集设置给阻塞信号集
函数原型:
int sigprocmask(int how, const sigset_t* set, sigset_t* oldset);
how: SIG_BLOCK(阻塞, 将set中被添加(等于1)的信号设置到阻塞信号集中)
SIG_UNBLOCK(解除阻塞, 将set中被添加(等于1)的信号从内核阻塞信号集中解除阻塞)
SIG_SETMASK(将set覆盖到到内核的阻塞信号集上)
4) sigpending -- 读取当前进程的未决信号集
函数原型: int sigpending(sigset_t* set);
参数: set -- 内核将未决信号集写入set
4) demo
// 手动屏蔽某些信号
sigset_t pendset;
sigemptyset(&pendset); // 集合置空
// 添加阻塞的信号
sigaddset(&pendset, SIGINT); // ctrl + c
sigaddset(&pendset, SIGQUIT); // ctrl + \ 3号信号
sigaddset(&pendset, SIGKILL);
// 设置给内核的阻塞信号集
sigprocmask(SIG_BLOCK, &pendset, NULL);
int i = 1;
while(1) {
sigpending(&pendset);
// 每隔一秒读取一次内存的未决信号集(1-31号信号)
for (i = 1; i < 32; i++) {
// 一次判断每个信号
if(sigismember(&pendset, i)) {
printf("1 ");
} else {
printf("0 ");
}
}
printf("\n");
sleep(1);
}
8> 信号捕捉
1) signal函数 -- 没有遵循poxy标准, 可能会因为操作系统的区别有所差异
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum <要捕捉的信号>, sighandler_t handler <回调函数地址|函数名>);
demo:
extern void touch_INT_SIG(int n);
main...
// 注册捕捉函数
signal(SIGINT, touch_INT_SIG);
while(1){}
return 0;
void touch_INT_SIG(int n) {
printf("捕获到 %d 号信号\n", n);
}
2) sigaction函数 -- 不会有差异
函数原型
int sigaction(
int signum, // 捕捉的信号
const struct sigaction* act,
struct sigaction* oldact
);
struct sigaction {
void (*sa_handler)(int), // 与signal(int, sighandler_t) 一样, 用于回调
void (*sa_sigaction)(int, siginfo_t*, void*), // 一般不用
sigset_t sa_mask,
// 作用: 再信号处理函数执行过程中, 临时的屏蔽掉某些信号, 放到sa_mask;(临时的, 函数执行结束后自动解除屏蔽)
// 如果没有使用的需求, 就进行清空操作: sigemptyset(sigset_t* set);
int sa_flags,
// 回调函数sa_handler或sa_sigaction
// 如果使用了sa_handler, sa_flags一定赋值为0, 信号将阻塞的系统调用打断后自动重启使用SA_RESTART
http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=3772700&page=1
void (*sa_restorer)(void); // 被废弃
};
demo:
extern void touch_sig_fun(int sig_id);
int main(int argc, char* argv[]) {
struct sigaction act;
act.sa_handler = touch_sig_fun;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
// 添加临时屏蔽
sigaddset(&act.sa_mask, SIGQUIT); // 再回调函数睡眠过程中, 暂不响应SIGQUIT信号, 等待执行回调结束之后再处理SIGQUIT信号
sigaction(SIGINT, &act, NULL);
while(1) {}
return 0;
}
void touch_sig_fun(int sig_id) {
printf("touch %d signal\n", sig_id);
sleep(3);
printf("wake up\n");
}
三: 共享映射区 - 不区分进程之间有无血缘关系
1> FIFO (first in first out), 先进先出(队列)
1) 特点
有名管道
在磁盘上有这样一个文件 ls -l -> p(p类型的文件)
伪文件, 在磁盘上的大小永远为0
数据存储在内核中对应的一个缓冲区
半双工的通信方式
2) 使用场景
没有血缘关系的进程间通信
3) 创建方式
1. 命令: mkfifo 管道名
2. 函数: mkfifo(char* pathname, mode_t mode); man文档第三章
4) 进程间通信(伪代码)
a. fifo文件 --- afifo
1. 两个不相干的进程 A(a.c), B(b.c);
2. a.c --> read
int fd = open(afifo, O_RDONLY);
read(fd , buf, sizeof(buf));
close(fd);
b. b.c --- write
1. int fd1 = open(afifo, O_WRONLY);
write(fd1, "hello, world", 11);
close(fd1);
5) 示例(写文件)
int main(int argc, char* argv[]) {
int ret = access("./1fifo", F_OK);
if (ret == -1) {
ret = mkfifo("./1fifo", 0664);
if (ret == -1) {
perror("create fifo error: ");
exit(1);
}
}
int fd = open("./1fifo", O_RDONLY);
if(fd == -1) {
perror("open fifo error: ");
exit(1);
}
char buff[1024] = {0};
while(1) {
memset(buff, 0, 1024);
read(fd, buff, 1024);
if(strncmp(buff, "bye", 3) == 0) {
break;
}
printf("buff: %s\n", buff);
}
close(fd);
printf("\n");
return 0;
}
2> mmap - 创建内存映射 - <sys/mman.h>
1) 作用: 将磁盘文件的数据映射到内存中, 用户通过修改内存就能修改磁盘文件
2) 函数原型:
void* mmap {
void* adrr, // 映射区首地址, 传入NULL 由系统指定, 返回值就是系统指定的内存地址
size_t length, // 映射区的大小 -- 4k的整数倍, 不能为0, 一般对剑多么大 length就多大
int prot, // 映射区权限
-- 对映射区的操作权限: PROT_READ(读, 必须要有)
-- PROT_WRITE(写, 可有可无)
int flags, // 标志位参数
-- MAP_SHARED(有PROT_WRITE权限, 修改了内存数据会同步到磁盘)
-- MAP_PRIVATE(有PROT_WRITE权限, 修改了内存数据不会同步到磁盘)
int fd, // 文件描述符 -- 内存映射区所对应文件的描述符fd
-- 如何得到的: open();
off_t offset // 映射文件的偏移量
-- 映射时候 文件指针的偏移量, 必须是4k的整数倍, 一般情况用0
}
3) 返回值: success -- 系统指定的映射内存首地址, faild -- MAP_FAILED ((void*) -1) -> perror();
4) munmap - 释放内存映射区
1. 函数原型: int munmap(void* addr, size_t length); -- 调用失败返回 -1, perror()
addr -- mmap的返回值;
length -- mmap的第二个参数
2. 思考问题:
1$: 对mmap的返回值(ptr)做++操作(ptr++), munmap是否能够成功
不能释放成功, 可以重新定义一个指针指向ptr;
2$: 如果open时O_RDONLY, mmap时prot参数指定PROT_READ|PROT_WRITE会怎样
报错: Permission denied
3$: 如果文件偏移量为1000会怎样
无效的参数, 必须是4096的整数倍
4$: 如果不检测mmap的返回值会怎样
不会怎么样, 导致出了错误不知道, 无法回收
5$: mmap什么情况下会调用失败
第二个参数为0
第三个参数没有PROT_READ权限, 或大于fd的打开权限
最后一个参数offset不是4096的整数倍(必须是4k的整数倍)
6$: 可以open的时候O_CREATE一个新文件来创建映射区吗
不能, 创建出来的文件大小为0, 或者对创建出来的文件进行拓展(lseek(fd, 0, SEEL_END)再写操作 | truncate(path, length))
7$: mmap后关闭文件描述符, 对mmap映射有没有影响
没有影响
8$: 对ptr越界操作会怎么样
段错误(操作已经被占用的内存)
随机数(操作未被占用的内存)
5) demo
int main() {
// 打开文件
int fd = open("file", O_RDWR);
if(fd == -1) {
perror("open file error: ");
exit(1);
}
int len = lseek(fd, 0, SEEK_END);
// 创建内存映射区
void* ptr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED) {
perror("mmap error: ");
exit(2);
}
// 打印映射区中内容
printf("%s", (char*)ptr);
// 释放映射区
munmap(ptr, len);
close(fd);
printf("\n");
return 0;
}
6)父子进程永远共享的东西
1. 文件描述符
2. 内存映射区(父进程创建的内存映射区, 子进程也能找到)
3. 父子进程共享映射区
int main() {
// 打开文件
int fd = open("file", O_RDWR);
if(fd == -1) {
perror("open file error: ");
exit(1);
}
int len = lseek(fd, 0, SEEK_END);
// 创建内存映射区
void* ptr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED) {
perror("mmap error: ");
exit(2);
}
pid_t pid = fork();
if(pid == -1) {
perror("fork error: ");
exit(1);
}
// 父子进程间的通信, 都通过ptr进行通信
if(pid > 0) {
// 写操作
strcpy((char*)ptr, “Hello World”);
// 回收子进程
wait(NULL);
} else if (pid == 0) {
// 读数据
printf("%s\n", (char*)ptr);
}
// 释放映射区
munmap(ptr, len);
close(fd);
printf("\n");
return 0;
}
7) 匿名映射区(父子进程, 共享文件描述符&内存映射区)
1. 代码
int main() {
// 创建匿名内存映射区
int size = 4096;
void* ptr = mmap(NULL, size, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0);
// fd: -1 不用文件
// MAP: MAP_SHARED | MAP_ANON(或者MAP_ANONYMOUS)
if(ptr == MAP_FAILED) {
perror("mmap error: ");
exit(2);
}
pid_t pid = fork();
if(pid == -1) {
perror("fork error: ");
exit(1);
}
// 父子进程间的通信, 都通过ptr进行通信
if(pid > 0) {
// 写操作
strcpy((char*)ptr, “Hello World”);
// 回收子进程
wait(NULL);
} else if (pid == 0) {
// 读数据
printf("%s\n", (char*)ptr);
}
// 释放映射区
munmap(ptr, len);
close(fd);
printf("\n");
return 0;
}
7) 没有血缘关系的进程间通信(不能使用匿名映射, 只能借助磁盘文件创建映射 hello)
不阻塞
1. 伪代码
a.c
int fd = open("hello", O_RDWR);
void* ptr = mmap(NULL, ,,,, fd, 0);
对映射区进行读写操作
对ptr进行操作
b.c
int fd = open("hello", O_RDWR);
void* ptr = mmap(NULL, ,,,, fd, 0);
对映射区进行读写操作
对ptr进行操作
1. demo代码
read.c
int fd = open("tmp", O_RDWR|O_CREAT, 0664);
ftruncate(fd, 4096); /// 使用ftruncate传入文件描述符给文件扩容
int len = lseek(fd, 0, SEEK_END);
void* ptr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED) {
perror("mmap: ");
exit(1);
}
while(1) {
sleep(1);
printf("%s\n", (char*)ptr + 1024); // 不从首地址读
}
int ret = munmap(ptr, len);
if(ret == -1) {
perror("munmap: ");
exit(1);
}
write.c
int fd = open("tmp", O_RDWR|O_CREAT, 0664);
ftruncate(fd, 4096); /// 使用ftruncate传入文件描述符给文件扩容
int len = lseek(fd, 0, SEEK_END);
void* ptr = mmap(NULL, len, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if(ptr == MAP_FAILED) {
perror("mmap: ");
exit(1);
}
while(1) {
char* p = (char*)ptr;
p+=1024;
strcpy(p, "hello read process\n");
sleep(2);
}
int ret = munmap(ptr, len);
if(ret == -1) {
perror("munmap: ");
exit(1);
}
四: 本地套接字domain - 稳定
server.c
/*************************************************************************
> File Name: server.c
> Author: lijin
> Mail:
> Created Time: Tue 28 May 2019 04:08:18 AM PDT
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stddef.h>
#include "wrap.h"
#define SERV_ADDR "serv.socket"
int main(int argc, char* argv[]) {
int lfd, cfd, len, size, i;
struct sockaddr_un serv_addr, cli_addr;
char buff[4096];
lfd = Socket(AF_UNIX, SOCK_STREAM, 0);
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, SERV_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path); /** serv_addr total len*/
unlink(SERV_ADDR); /** 确保bind之前serv_socket文件不存在, bind会创建该文件 */
Bind(lfd, (struct sockaddr*)&serv_addr, len); /** 参3不能是sizeof(serv_addr) */
Listen(lfd, 128);
printf("Accept...\n");
while (1) {
len = sizeof(cli_addr);
cfd = Accept(lfd, (struct sockaddr*)&cli_addr, (socklen_t*)&len);
len -= offsetof(struct sockaddr_un, sun_path); /** 得到文件名的长度 */
cli_addr.sun_path[len] = '\0'; /** 确保打印时, 没有乱码出现 */
printf("client bind filename %s\n", cli_addr.sun_path);
while((size = read(cfd, buff, sizeof(buff))) > 0) {
for (i = 0; i < size; i++) {
buff[i] = toupper(buff[i]);
}
write(cfd, buff, size);
}
close(cfd);
}
return 0;
}
client.c
/*************************************************************************
> File Name: client.c
> Author: lijin
> Mail:
> Created Time: Tue 28 May 2019 04:08:33 AM PDT
************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <pthread.h>
#include <semaphore.h>
#include <sys/socket.h>
#include <ctype.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <stddef.h>
#include "wrap.h"
#define SERV_ADDR "serv.socket"
#define CLIE_ADDR "clie.socket"
int main(int argc, char* argv[]) {
int cfd, len;
struct sockaddr_un serv_addr, cli_addr;
char buff[4096];
cfd = Socket(AF_UNIX, SOCK_STREAM, 0);
bzero(&cli_addr, sizeof(cli_addr));
cli_addr.sun_family = AF_UNIX;
strcpy(cli_addr.sun_path, CLIE_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(cli_addr.sun_path); /** 计算客户端地址结构有效长度 */
unlink(CLIE_ADDR);
Bind(cfd, (struct sockaddr*)&cli_addr, len); /** 客户端也需要bind, 不能依赖自动绑定 */
bzero(&serv_addr, sizeof(serv_addr)); /** 构造server 地址 */
serv_addr.sun_family = AF_UNIX;
strcpy(serv_addr.sun_path, SERV_ADDR);
len = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path); /** 计算服务器端地址结构有效长度 */
Connect(cfd, (struct sockaddr*)&serv_addr, len);
while(fgets(buff, sizeof(buff), stdin) != NULL) {
write(cfd, buff, strlen(buff));
len = read(cfd, buff, sizeof(buff));
write(STDOUT_FILENO, buff, len);
}
close(cfd);
return 0;
}
网友评论