基础IO
回顾标准库IO接口:
最常用:、
fopen:
r :只读方式打开文件
r+ :读写方式打开文件
w:只写方式打开文件,文件不存在则创建,存在则清空内容
w+:读写方式打开文件,文件不存在则创建,存在则清空内容
a:写 追加方式打开文件,文件不存在则创建,每次写入数据都是些在文件末尾
a+:可读,写,追加方式打开文件,文件不存在则创建,读数据的起始位置在文件起始,写数据的初始位置一直追加在文件末尾
fclose , fwrite , fread , fseek ,
fprintf , sprintf , snprintf , printf :
fgets ,获取一行数据
sscanf:把一串数据串。。。。
标准库IO接口的操作句柄是: FILE*----文件流指针
衍生类型:stdout, stdin , stderr -》FILE*
系统调用接口
open:
patnname:文件路径名
flags : 选项标志
必选项:必选其一
O_RDONLY 读
O_WRONLY 写
O_RDWR 读 写
可选项:
O_CREAT 文件不存在则创建,存在则打开
O_EXCL 与O_CREAT 一起使用时,若文件存在则报错
O_TRUNC 打开文件的同时截断文件内容,长度为0
O_APPEND 写追加模式
mode :创建文件时给定权限(八进制数字)
mode & (~umask)取得最终权限
返回值:文件描述符---正整数 错误返回 -1
write 向文件写入数据
fd: 打开文件返回的文件描述符
buf: 要向文件写入的数据
count : 要写入的数据长度
返回值: 实际的写入字节数, 错误返回 -1
read 读取文件数据
同write
buf: 对读取到的数据进行存储的位置
count : 要读的数据长度
返回值: 实际的读取字节数 错误:-1
close 关闭文件
lseek 跳转读写位置
fd:....
offset: 偏移量
whence: 偏移位置
SEEK_SET 文件起始位置
SEEK_CUR 文件当前位置
SEEK_END 文件末尾位置
返回值: 返回当前位置到文件起始位置的偏移量(可作为文件大小)
unlink
ftruncate
fileno
文件描述符和文件流指针:
标准库接口使用文件流指针 FILE* fp
系统调用接口使用文件描述符 int fd
库函数与系统调用接口关系:上下级调用关系
标准输入 stdin->0 宏STDIN_
标准输出 stdout->1
标准错误 stderr ->2
文件流指针这个结构中就包含了文件描述符成员,但我们使用标准库接口进行IO,则最终通过文件流指针描述符进而对文件进行操作。 缓冲区!
内核态/用户态
用户态:进程运行在用户态指的是当前操作,操作的都是用户空间
内核态:进程运行在内核态指的是当前完成功能是操作系统内核进行完成,操作内核空间
文件描述符是什么?
文件描述符是一个正整形数字,为什么可以通过这个数字操作文件?
文件描述符实际上就是一个数组下标,当进程没打开一个文件都会使用sturct file描述这个文件,
并且将描述信息添加到struct file这个结构中的file结构数组中,并且向用户返回数组下标作为文件描述符,用户通过文件描述符进行操作,
在内核实际上市通过文件描述符找到文件描述信息,进而操作文件----该结构体存在于pcb结构体中
文件描述符分配规则
最小位使用
重定向 :>> >
dup2(int oldfd, int newfd)
将oldfd 指向newfd
如果newfd已经打开, 则会关闭newfd
文件系统:Linux-ext2() 每一个文件分区都有文件系统
磁盘分区
超级块
存储整个文件系统的描述信息
提供下面的各个节点的起始和信息
inode表/块
内容
name
size
perm
addr
指向数据存储在数据块的位置
数据块
存储文件数据/内容 别的都不管
inode_bitmap(位图)
用于找到空闲节点,空闲数据块
data_bitmap(位图)
用于找到空闲节点,空闲数据块
文件存储流程:数据款存储数据 inode存储信息
文件读取流程:
软连接/硬链接: 目的都是为了访问源文件
创建软/硬链接的方式:ln -src.file hard.file in-s src.file soft.file
区别:
软连接是一个单独的文件,硬链接只是一个文件别名(目录项)
软连接就以一个文件指向源文件的 Inode 继而找到源文件
软连接可以跨分区
硬链接就是一个目录项, 记录和源文件一个inode 继而找到源文件
无法跨分区
链接库的生成与使用 ------>静态库/动态库(动态库会有偏移量)
生成(将用户实现的目标代码打包为库文件)
将源代码生成目标代码: gcc-fPIC -c test.c - o test.o
动态库: -fPIC 打包库文件 gcc--share ./.o -o lib***.so
静态库: ar - cr lib***.a./*.o
使用(链接):如果当前有同名的动态库和静态库,先链接动态库
生成可执行程序时链接库文件:
/lib64/usr/lib64.... LIBRARY_PATH= ./lib gcc-L ./lib
gcc main.c -o main-L ./lib -l test
运行可执行程序时加载库文件:针对动态链接
/lib64/usr/lib64...
LD_LIBTRARY_PATH=./lib
库的链接使用和运行加载的时候都需要在指定目录下:/lib64
-L path 指定库的链接查找路径 gcc a.c -o a -L . -lmytest
-l 指定要链接的库名称
LABRARY_PATH 指定库的链接查找路径
LD_LABRARY_PATH 指定动态库的运行加载路径
IPC:(进程兼通信)
基本介绍:操作系统提供给用户的集中进程兼通信方式及接口(因为进程具有独立性所以需通信方式,并且因为通信场景不同,因此提供的进程间通信方式也有多种)
进程的独立性
通信场景(数据传输,数据共享,进程控制,事件通知)
管道,实现数据流传输
内核中的一块缓冲区
多个进程通过访问同一个缓存区进而实现通信
共享内存,数据共享
消息队列,数据块传输
信号量,实现信号间同步与互斥
管道:本质是内核中的一块缓冲区-----半双工通信(双向选择单项通信)
匿名管道:只能用于具有亲缘关系的进程通信----缓存区没名字/具体标识符因此其他进程无法直接进行操作,只能通过子进程复制父进程的方式获取到管道的操作句柄(两个文件描述符fd[2],fd[0]用于读,fd[1]用于写) pipe
命名管道:可以用于同一主机任意进程间通信,因为命名管道有名称(名称就是创建的管道文件,可见于文件系统),因此进程都可以通过打开同一个管道文件而获取到同一个管道句柄。 mkfifo
有一个标识符,
管道的读写特性:虽然管道提供了双向选择,但是如果没有用到某一端,就把这一段关闭掉
读写特性
若管道中没有数据,则read会阻塞
若管道中数据满了,则write会阻塞
若管道所有写端关闭,则read读完所有数据返回0,
若管道所有读端关闭,则write触发异常
管道自带同步与互斥:管道进行数据读写大小不超过plpe_buf,则读写受保护
同步:临界资源的访问时序可控(我操作完了你才能操作)
互斥:临界资源的唯一访问(我操作的时候你不能操作)
管道提供流式服务:面向字节流数据传输---传输灵活但是会造成数据粘连----因为对数据边界不敏感
传输灵活:可以一点一点传输,也可以一次性传输
生命周期随进程
共享内存:最快的进程兼通信方式
共享内存的本质:在物理内存上开辟一块空间,将这块空间映射到进程的虚拟地址空间中,进程则可以通过虚
拟地址进行访问;如果一块内存被多个进程映射,那么多个进程访问同一块内存,则可以实现通信
共享内存直接通过虚拟地址操作进行通信,其他进程间通信方式都需要跟内核缓冲区进行数据交互(管道需要
将数据拷贝到内核缓冲区,然后再从缓冲区读出来);少了两部数据拷贝过程所以最快
操作流程:创建->建立映射关系->内核操作->解除映射关系->删除(删除一块共享内存,并不会立刻删除,而
是判断映射连数,若为0则删除,不为0则拒接后续连接,直到为0删除)-----shmget->shmat->memcpy-
>shmdt->shmctl
消息队列:创建——>添加数据节点->获取数据节点->删除(msgget-msgsnd--mssrcv--msgctl)------数据块传输
他传输的是有类型的数据块,用户可以根据自己的需要,选择性的获取某些类型的数据
本质就是内核中的一个优先级队列
多个进程通过同一个标识符访问同一个消息队列,通过添加、获取节点时间进程间通信
信号量:内核中的计数器----具有等待队列(具有等待和唤醒功能)
用于资源计数
若计数<=0则表示没有资源,没有资源需要等待
若计数>0,则表示有资源,则可以获取资源且资源-1
如果放置了资源,则计数+1,并且唤醒等待进程
实现进程间的同步与互斥
信号量:实现进程间同步与互斥(实质内核中的计数器---对资源进行计数,实现同步,用于判断当前进程能不能
对临界资源进行访问)
同步:保证进程间对临界资源的访问时序可控(我操作完了你才能操作)
互斥:保证进程间对临界资源的安全性(我操作的时候你不能操作)
同步保证时序合理:让进程在能访问的情况下访问临界资源的情况下放行,否则需要让进程进行等待;等到
其他进程促使资源访问资源条件满足后,唤醒等待的过程,去进行访问临界资源
互斥保证访问安全:同一时间只允许一个进程访问临界资源,避免数据操作的二义性,使用两种状态来标
记,临界资源当前是否允许访问,一个进程访问之前应该询问状态,并且访问期间需要将状态标记为
不可访问,避免其他进程争抢
标记 用0/1来 控制是否能访问
进程信号:软件中断----通知事件发生(打断当前的(阻塞)操作,去处理事情)
信号的生命周期:产生---->注册--->注销--->处理 阻塞
产生:软件/硬件
注册:在pcb中标记信号的到来
注销:
处理:默认/忽略/自定义
信号有不同的种类:每个信号都对应不同的事件
Linux下信号的种类:
kill -l 查看所有信号种类
共62种信号 :1-31号信号每个都有对应的事件 34-64后续添加的信号
1-31也叫非可靠信号/非实时信号(指新信号可能会丢失) 34-64为可靠信号/实时信号(不会丢失)
信号的产生:
硬件产生信号:ctrl + c ctrl + z ctrl + l
软件产生信号:kill - signo - p pid命令
‘kill() raise() abort() alarm()[定时器] 4个函数的使用方法
程序异常
core dumped 核心转储,程序异常退出是保存程序运行信息用于事后调试,程序异常时,保存运行信息到
core.pid文件中,方便事后调试
core dumped--默认关闭。----占磁盘资源,安全性考虑
ulimit -a 查看coredump是否开启
ulimit -c1024 设置核心转储文件大小,开启核心转储
gdb./loop -->core - file core.pid -->bt
信号在进程中注册:修改pending 未解决信号集合(位图)中对应信号位+添加sigqueue节点
task_struct -> struck sigpengding -> sigset_t signal(是一个位图,用来标记是否有一个信号到来)
非可靠信号的注册:判断是否有相同的未决信号(未处理的信号),若有则什么也不做(时间丢失),否则修改位图添加节点
可靠信号注册:判断是否有相同未决信号,若没有则修改位图添加节点,否则直接添加节点
信号在进程中注销:删除信号sigqueue节点,并且修改pending位图
非可靠信号注销:直接删除节点,修改位图(非可靠信号只会注册一次)
可靠信号注销:删除节点,判断是否还有相同信号,若有则位图依然为置1,若没有则修改为0
信号的处理:
信号默认处理方式
信号忽略处理方式
信号自定义处理方式:
typedef void(*sighandler_t)(int)
signal(signo , sigcb)
sigaction(signo , struct sigaction new , old)
task_struct - >strict sighand
自定义信号处理方式的捕捉流程:用户态切换到内核态运行,准备从内核态切回用户态运行的时候,去处理信
号若信号为默认/忽略处理,则在内核中直接完成,但是如果自定义处理方式,则需要返回用户态执行信号
回调函数,完毕后回到内核态。。。。没有信号了则回到程序主流程
信号阻塞:阻止信号被递达(暂时不处理信号----给信号做标记 递达:信号的处理-
在进程pcb中做标记,标记哪些信号来了暂时可以不处理;
在进程pcb中有一个信号的阻塞集合,信号阻塞就是在这个集合中做标记
sigprocmask:对信号阻塞集合进行操作的接口
SIG_BLOCK:block = block | set
SIG_UNBLOCK :block = block & ~set
SIG_setmask block = mask
sigemptyset sigfillset sigaddset sigdelset sigismember
sigsuspend:临时使用传入的mask替换block集合,陷入休眠,被唤醒后将block集合还原
竞态条件:运行时序的竞争执行
函数的可重入与不可重入:一个函数是否可以在多个运行时序中重复调用而不会出现问题,在一个函数是否进
行了对全局数据的非原子性操作
可重入:同时重复进入同一个函数执行程序不会造成数据二义性以及逻辑混乱
不可重入:同时重复进入同一个函数执行程序又可能会造成数据二义性以及逻辑混乱
可重入与不可重入的关键点:在一个函数是否进行了对全局数据的非原子性操作
关键字:volatile:保存内存可见性-----防止编译器过度优化---每次对变量访问都要从内存重新获取数据
SIGCHLD:子进程退出,操作系统通知父进程的信号----是一个非可靠信号
自定义SIGCHLD信号处理方式sigcb,当子进程退出,操作系统发送信号给父进程,直接触发信号回调sigcb,
用户主要在sigcb调用wait/waitpid就可以处理子进程的退出
假如有多个子进程同时退出,则有可能造成时间丢失,导致sigcb只被回调一次,只处理了一个子进程,所以
在sigcb中用户需要循环非阻塞处理子进程退出,直到没有退出的子进程(无法处理)
while(){waitpid(-1,NULL.WONOHANG) > 0 }//因为>0表示有子进程退出
必须用非 阻塞否则没有子进程退出时,waitpid将阻塞,导致程序无法回到主控流程
Linux------基础IO+进程间通信+进程信号
最新推荐文章于 2023-04-15 10:59:04 发布