1、内存映射MMap:
DMA:
可以用*/[]取代read和write;
限制:
1、文件大小固定不能改变;(ftruncate)
2、只能是磁盘文件;
3、建立映射之前先open
mmap函数:
mmap第一个参数填空指针,分配映射空间时将自动分配到堆空间;分配区域首地址作为返回值返回,
length大小要固定;
prot表示内存是否可读可写;
prot的可选项:可以用按位或连接
flages:属性(MAP_SHARED(多进程共享映射区));
fd:映射的文件;(文件描述符)
offfset偏移量:0;(直接写0);
然后通过munmap释放;
该函数只有length和fd要改变;
代码实现通过mmap函数再内存中直接通过映射修改文件内容:
#include <43func.h>
int main(int argc, char *argv[]){
ARGS_CHECK(argc,2);
int fd = open(argv[1],O_RDWR);
ERROR_CHECK(fd,-1,"open");
int ret = ftruncate(fd,5);
ERROR_CHECK(ret,-1,"ftruncate");
char *p = (char *)mmap(NULL,5,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
for (int i = 0; i < 5; i++)
{
printf("%c",p[i]);
}
printf("\n");
p[0] = 'H';
p[1] = '1';
for (int i = 0; i < 5; i++)
{
printf("%c",p[i]);
}
munmap(p,5);
}
2、lseek:也可与诱发文件空洞
- fd:文件描述符,指向要操作的文件。
- offset:相对于whence的偏移量,可以是正数也可以是负数。
- whence:偏移量的起始位置,它可以是以下常量之一:
- SEEK_SET:文件的开头。
- SEEK_CUR:当前的读写位置。
- SEEK_END:文件的末尾。
内核态和用户态各有一个缓冲区,lseek修改内核态,而fseek修改用户态;
3、文件流也使用了文件对象:
int fileno():传入一个文件流指针则返回一个文件描述符;
用户,库作者,接口的关系:
用户通过接口与系统进行交互,而库作者通过API提供库的功能和服务。接口和API是连接用户和库作者的桥梁。
为了降低耦合性,库作者把库代码进行封装,提供接口给用户;所以用户通过接口访问库;
代码即注释:
用有意义的符号取代固定的数字:
3、文件描述符的复制 dup
数值不同,偏移量共享
dup();选择一个最小可用的fd和oldfd同指向;
查看stdin,stdout,stderr的文件描述符
使用printf本质上往stdout写入数据;
printf对应的文件描述符为1:
往1号文件写入数据相当于往标准输出中写入数据;
遵循代码即注释原则:用STDERR_FILENO代替1
当文件通过close关闭标准输出时,再次用printf就相当于往重定向;
先打开open,还需要重定向文件描述符;可以使用dup();
用dup复制的文件描述符时,新的文件描述符和旧的文件描述符都共享同一个指针,(一个文件描述符写了,另一个接着写);
当关闭oldfd时,还可以继续用newfd进行操作;当文件描述符完全关闭后才会释放文件对象。
这种设计允许进程在需要时复制文件描述符,并在不同的线程或进程间传递它们,而不会导致底层资源的立即释放。(引用计数)
引用计数
是指将资源(如对象、内存或磁盘空间等)的被引用次数保存起来,当被引用次数变为零时,系统将自动释放该资源的过程。
重定向到目标文件:
先打开文件再把标准输出重定向到文件里;
重定向到testFile,当关闭标准输出时printf失效,把printf对应的输出对象改成了testFile;
从目标文件重定向回自定义目标:
int dup2(int oldfd,int newfd);
让newfd与oldfd指向同一个文件对象;
如果newfd已有指向就会自动close;
通过把dup2把标准输出的文件描述符改成savefd,再把标准输出的屏幕对象改成文件对象,最后再改回来,把屏幕定向到文件,再恢复;
4、有名管道(name pip / FIFO):先进先出;
进程间通信机制在文件系统的映射;
通信的传输方式:
单工:A->B
半双工:A->B , B->A 不同时 (管道至少是半双工通信)
全双工:A<-------->B
mkfifo :创建管道
.
mkfifo 1.pip
管道不能存数据,只能暂存,不能持续存储;不能用vim打开
管道的半双工通信:用两个终端展示(右边取出数据,左边取消阻塞,把左边进程的数据传输到右边进程)
先读取数据,没有数据会进程阻塞,先传输,没有读也会阻塞;
用系统调用操作管道:
半双工通信管道
open:O_WRONLY(写端)
O_RDONLY(读端)
(用open打开写端或读端);
管道实在open的时候阻塞的。当一个进程open管道一端的时候,进程处于阻塞状态,如果对端未被打开,进程处于阻塞状态,直到对端被另一个进程打开;(半双工通信)
全双工通信:(使用两个管道实现)
死锁的产生:
错误示范:
chat1 打开1pipe的读在等待chat2 在1pipe的写;
chat2 打开2pipe的写在等待chat2 在2pipe的读;
进入循环等待状态,产生死锁;占用资源的顺序出现了问题(调整顺序解决);
全双工通信:
通过死循环实现一直通信;
该程序只能实现一问一答:要等回复后才能看到下一条问题;
死循环读管道,读stdin,(目前代码给了固定的相应的读取顺序)
当B一直发,B的标准输入会堆积数据,但是A没有回复数据;
read fdr 和read stdin 存在先后顺序;(两个read的动作都会阻塞)
后面的数据就绪,但前面的程序阻塞了;数据串联(一个阻塞全部阻塞);
程序采用了一个阻塞式的读取循环,每次从管道读取数据并打印,然后清空缓冲区,接着从标准输入(STDIN_FILENO
)读取数据并写入管道。问题在于,当从标准输入读取数据时,程序会阻塞直到有数据可读。这意味着,如果另一进程已经发送了第二条消息到管道中,但是当前进程没有从标准输入读取任何数据,那么它就不会处理管道中的第二条消息。
IO多路复用:(现代服务器的基础)
采用数据并联解决;
select:
fd_set : 监听集合;
#include <sys/select.h>
#include <sys/time.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds
:这是一个整数值,指定了被监听的文件描述符集合中最大文件描述符值加 1。通常,它会被设置为集合中最大文件描述符的加一。readfds
:指向一个文件描述符集合的指针,该集合中的文件描述符被监视以检查是否可读。writefds
:指向一个文件描述符集合的指针,该集合中的文件描述符被监视以检查是否可写。exceptfds
:指向一个文件描述符集合的指针,该集合中的文件描述符被监视以检查是否有异常条件待处理。timeout
:指向一个timeval
结构的指针,该结构指定了select
的超时时间。如果设置为 NULL,则select
会无限期地等待,直到某个文件描述符就绪。
fd_set
类型是一个位掩码,用于存储多个文件描述符的状态。在调用 select
之前,程序会使用如 FD_ZERO
、FD_SET
、FD_CLR
和 FD_ISSET
等宏来操作这些集合。
(1)创建监听集合
(2)设置合适的监听:FD_ZERO(清空) FD_SET(加入监听){把会阻塞的文件描述符加入到监听}
(3)调用select函数,会让进程阻塞;
(4)当监听的文件描述符fd中有任何一个就绪时则select就绪;
(5)轮流询问(轮询)所有监听的fd是否就绪,FD_ISSET(询问)
使用IO多路复用实现进程之间聊天;