导图社区 IO进线程
这是文件IO和进线程的一些学习笔记,无论你是初学者还是有一定经验的开发者,相信这些学习笔记都能为你提供宝贵的参考和启示。
编辑于2024-10-15 22:18:26IO进线程
用别人写好的程序
熟悉使用所有的API/接口/函数,达到有一个函数说明就能够自主使用它的程度
掌握IO进线程相关概念
文件流指针就是系统将文件描述符封装后得到的,库函数通过文件流指针操作文件,系统调用接口通过文件描述符操作文件
以下的通信都是依赖LINUX内核在单机上进行进程的通信,不能多机通信
LINUX下一切皆文件
标准IO
标准C库提供
标准C库其实也在调用系统调用
获取文件
目录操作
打开目录
返回:成功返回目录流指针,错误返回NULL
DIR* opendir(const char *name)
打开指定目录
name:目录路径
读取目录
struct dirent *readdir(DIR *dirp)
参数
dirp:指针流
一次只能读取一个文件,一般循环读取
返回:NULL读取到目录流末尾,或者产生错误,成功返回一个概述当前目录文件的结构体(struct dirent)
指针的移动
void seekdir(DIR *dirp,long loc)
没有返回值
dirp:流指针
loc:跳转的位置,telldir的返回值
loc指向dirp目录流中应该跳转到的位置
long telldir (DIR *dirp)
返回当前目录流的位置
关闭目录
int closedir(DIR *dirp)
获取文件的详细属性
int stat(const *pathname,struct stat *statbuf) int fstat(int fd,struct stat *statbuf) int lstat(const char *pathname,struct stat *statbuf)
参数
statbuf存储文件信息的结构体地址
返回:成功0,失败-1
struct passwd *getpwuid(uid_t uid)
根据用户ID获取用户信息,返回一个指向passwd的结构体指针
失败返回NULL
struct group *getgrgid(gid_t gid)
子主题
时间相关函数
time_t time(time_t *tloc)
time_t t=time(NULL)
ctim
localtime
返回:成功秒数
char *ctime (const time_t *timep)
把秒数转换为一个时间的字符串
成功返回字符串,失败返回NULL
操作文件的步骤
打开文件
fopen
FILE*fopen(const char *pathname,const char *mode) 返回:成功:返回文件流指针,失败:NULL 参数:pathname是路径 mode -r:只读,文件开头读,不能创建 mode -r+:读写,文件开头读写,不能创建,写覆盖 mode -w:只写,文件开头写,能创建,清空文件 mode -w+:读写,文件开头,能创建,清空文件 mode -a:只写,文件末尾,能创建,不会清空文件 mode -a+:读写,文件末尾,能创建
读取/写入
fread(按类型读取规定的长度)
size_t fread(void *ptr,size_t size,size_t nmemb,FILE *stream)
从数据流读取数据到内存缓冲区
读取图像,音频
返回
成功:返回读取到的类型个数
失败或者文件末尾0
参数
ptr:存数据的地址
size:数据项的大小
nmemb:取的类型个数
fgetc(读一个字符)
int fgetc(FILE *stream)
返回值
成功:返回一个unsigned char 强转为int
失败或者读到文件末尾为EOF
参数:stream流指针
fgets(读一行)
char *fgets(char *s ,int size,FILE *stream)
返回值
成功:返回s
失败或者文件末尾NULL
参数
s:存储读取的内容空间首地址
size
s
空间大小
>
读取一行内容末尾补0,回车也会读进去
<
读取size—1个内容,末尾补0
fputc(写入一个字符)
int fputc(int c, FILE *stream),\0不会写入
返回
成功:返回写入的字符
失败:EOF
参数
c:要写进去的字符
stream:流指针
fputs(写一个字符串)
int fputs(const. char. *s, FILE *stream)
返回
成功:非负
失败:EOF
fwrite(将ptr地址开始,将数据写入stream)
size_t fwrite(const void*ptr,size_t size(写入类型的字节大小),size_t nmemb(写入元素的个数),FILE *stream)
返回值
成功:返回写入的字节数
失败:0
参数
ptr:写入的内容的地址
size:按照什么类型写
fprintf()
int fprintf(FILE *stream,const char *format,......)
返回
成功:返回字符数量(不包括用于字符串输出结尾的空字符)
失败:返回一个负值
fclose
关闭
int fclose(FILE *stream)
返回0和EOF
fseek(指针偏移)
int fseek(FILE *stream,long offset,int whence )
返回:成功0,失败-1
offset:左边是负数,右边是正数,偏移量
whence:从哪里开始偏移
让指针偏移到文件末尾int ftell(FILE *stream)
返回:成功返回指针的偏移量,失败返回-1,可以用来计算文件的大小
关闭
fclose
优点:移植性好,有缓存
缓存:没有暂存
行缓存
标准输出是行缓存
刷新条件
/n
缓存区满1024
强制刷新 fflush
大部分程序结束fflush
文件流指针:结构体指针,存储了文件的相关信息(文件描述符 fd)
操作系统开启会自动开启三个文件
标准输入stdin
标准输出stdout
标准错误输出stderr
int fflush(FILE *stream) perror刷新缓存 返回:成功0,失败EOF
全缓存
刷新条件
缓存区满4096
setvbuf
int setvbuf(FILE *stream,char *buf,int mode,size_t size)
设置文件缓存类型
返回
成功:0
失败非0
参数
stream:文件流指针
buf缓存区的首地址,NULL默认
mode:
_IONBF无缓存
_IOLBF行缓存
_IOFBF全缓存
强制刷新
无缓存
标准错误输出
文件IO=系统调用
操作系统提供
只有系统调用才能调用操作系统
主要对文件描述符操作
缺点:移植性差,没有缓存
操作系统开启会自动开启三个文件
标准输入1
标准输出0
标准错误输出2
文件操作
打开文件open
返回:成功返回文件描述符,失败返回-1
int open(const char *pathname,int flags)不会创建新文件,单纯打开文件 int open(const char *pathname,int flags,mode_t mode)可以创建新文件
mode
只有flags用O_CREAT时才用
flags
O_RDONLY,只读
r
O_WRONLY,只写
O_RDWR,读写
O_CREAT:只写creat文件存在就打开,不存在就创建
O_EXCL:检测文件是否已经存在,通常与O_CREAT一起使用
检测文件是否存在
O_TRUNC:清空文件
O_APPEND:已添加方式打开文件,对文件的写操作都在文件末尾
读取文件/写入文件read/write
size_t read(int fd,void *buf,size_t count)
把fd的前count数据读取到buf
参数
fd:描述符
buf:接收的地址
count:接收的字节数
返回:成功返回读取到的字节数,0代表读到文件末尾,-1代表错误
size_t write(int fd, const void*buf,size_ t count)
把buf的前count数据写入fd
返回:-1错误,正确返回写入数据的大小
指针偏移lseek
off_t lseek(int fd, off_t offset,int whence)
offset偏移大小,正数向前取,负数向后取
whence
SEEK_SET文件开始位置
SEEK_CUR文件当前位置
SEEK_END文件末尾位置
返回:-1错误,成功返回偏移后的位置
关闭文件close
int close(int fd)
库
编译,汇编,连接(连接的是库)
减少编译时间,商业用途,
静态库
编译时会把库的内容编译到程序
优点:运行时不需要环境
缺点:体积大
把.c编译成为.o文件 gcc -c add.c -o add.o
把.o编译成为libxxx.a文件 ar crs
使用ar命令将.o文件打包(归档)
得到:libxxx.a
动态库
编译时不会把库的内容编译到程序
优点:体积小
缺点:运行时需要环境
进线程
进程
程序执行一次的过程(创建,调度,消亡),动态,内存
进程是程序执行和资源分配的最小单元(PCB)
组成
正文段,数据段(全局),系统数据段(环境变量,命令行参数,PCB存储进程相关信息的结构体在内核)
PID:进程ID,唯一的身份标识,固定不变的
分类:交互进程,批处理进程,守护进程
状态:运行态(R),停止态(T),等待(S)可中断,等待(D)不可中断,僵尸(Z)
相关命令
kill
kill -9 pid
强制结束进程
ps
显示当前系统的进程状态,非动态
aux
查看所有进程信息
auxr
查看正在活跃的进程
aux
top
动态查看,监视
每3秒刷新一次
jobs
bg把后台程序,在后台执行
fg把后台进程放到
进程相关接口
创建进程
pid_t fork(void)
独立空间
完全拷贝父进程环境,数据不共享
执行次序
父子进程间执行次序不确定
pid_t vfork(void)
创建一个子进程,在没有调用exec或者exit之前共用一片空间,避免不必要的内存拷贝
子进程先于父进程执行,在exec后父进程才会被调度运行
创建一个子进程,会复制父进程所有的东西
返回:父进程返回pid,子进程返回0,失败-1
子主题
回收进程处理
孤儿进程,父进程先于子进程结束,子进程会被init(操作系统运行的第一个进程)进程收养
僵尸进程:子进程退出,父进程没有回收资源,子进程的资源需要父进程回收
getpid(获取自己的pid)
getppid(获取父亲辈的pid)
void exit 退出进程
刷新缓存区
void_exit
退出不刷新缓存区
pid_t wait (int *status)
等待回收子进程的资源,阻塞
参数
status:一个整型的地址,用于接收退出状态,NULL不接收状态
当父进程除了回收子进程资源外,还要做其他事情,那么无法实现
pid_t waitpid(pid_t pid,int *status ,int options)
子主题
exec函数族
功能:在一个进程内部调用另外一个进程,谁用谁死
取代原调用进程的数据段,除了进程号,其他全部被新进程替换
主要用于区别参数传递的方式和是否包含环境变量
控制第二个参数
l
列举,参数需要一个一个列举出来
int execl(const char *path,const char *arg,......,(char*)NULL) int execlp(const char *file,const char *arg,......(char*)NULL) int execle(const char *path,const char *arg,......,char *const envp[],char* const *argv[]) int execv(const char *path,char * const argv[]) int execvp(const char *file,char * const argv[]) int execvpe(const char *file,char *const argv[],char *const envp[])
v
把运行的指令放到一个字符串数组中
控制第一个参数
p
第一个参数只需要写程序的名字
控制第三个参数
e
会多一个参数,传递自定义环境变量
进程间通信IPC
管道
有名
支持非亲缘进程使用
创建mkfifo 名字可以是文件也可以是函数
管道文件,没有大小
int mkfifo(const char *pathname,mode_t mode)
参数
路径和权限
返回:成功0失败-1
无名
要求确定读端和写端,只能亲缘进程使用
特性:当读端存在,写端不存在,读端会读到最后一次内容返回0:当读端不存在,如果再写入写端,会爆炸,程序结束
int pipe(int pipefd[2])
0写
1读
返回:0和-1
参数:2个元素的整型数组,一定在fork之前使用
信号
信号有限
异步通信
SIGHUP:
SIGINT
int=ctrl C控制终止
SIGQUIT
quit=ctrl-\,控制终止
SIGILL
SIGFPE
SIGKILL
立即结束程序的运行,并且不能被阻塞,处理和忽略
SIGALRM
定时器到时的时候发出
SIGSTOP暂停一个进程,不能结束,忽略和处理
SIGCHLD
子进程改变状态时,父进程会收到这个信号
函数
int raise(int sig)
给自己发送一个信息
返回值:成功0和-1失败
参数:sig信号
int kill(pid_t pid,int sig)
给进程号为pid的进程发送一个信号
返回:0和-1
sig:信号
void(*signal(int signal,void(*hander)(int )))(int )
void(*signal)(int) void(*hander)(int)
参数
sig:信号
handler
忽略:SIG_IGN
默认:SIG_DFL
自定义函数的指针
回调函数的参数是触发的信号编号
返回:成功返回上一次的信号处理函数地址,失败-1
void(*)(int)
void (*sighander_t)(int) sighander_t signal(int signum,sighander_t handler)
此函数必须在signal()被调用前申明,hander是这个函数的名字,
当指定的信号到达时就会跳转到参数hander指定的函数执行
void(*)(),函数指针类型 (void(*)())0,将0强制转化为函数指针类型
unsigned int alarm(unsigned int seconds)
闹钟函数,设置定时器
返回:成功返回上一次,距离触发的剩余时间,失败-1
int pause(void)
挂起程序,等待信号
返回-1
接受到SIGCHLD才被唤醒
共享内存
最快的进程间通信
内存映射
系统调用
关联
ipc通信
创建ipc对象,不删除一直在,占用空间
步骤
创建共享内存
映射
操作*p=1
取消映射
释放
gcc xxx.c -o xxx
函数
创建共享内存int shmget(key_t key,int size,int shmflg)
第一个参数的确定(key)key_t ftok(const char*pathname,int proj_id)
生成一个唯一序号
返回:成功返回唯一值,失败-1
pathname:路径
id:字符和数字都行
打开/创建共享内存
返回:成功返回共享内存的标识,失败-1
参数
size:共享内存大小
shmflg:权限
IPC_CREAT:表示创建共享内存
IPC_EXCL和IPC_CREAT,一起使用时,共享内存已经存在则创建失败
映射
void *shmat(int shmid,const void*shmaddr,int shmflg)
返回:返回映射之后的地址
参数
shmid:共享内存标识
shmaddr:映射到哪里去,NULL默认
shmflg:0默认读写,SHM_RDONLY只读
功能:将共享内存段连接到当前进程的地址空间
操作
取消映射
int shmdt(const void*shmaddr)
功能:断开共享内存段与当前进程的连接
删除共享内存
int shmctl(int shmid,int cmd,struct shmid_ds*buf)
同步互斥
消息队列
可以缓存,可以自己规定接收顺序
削峰:大数据处理
解耦:
创建消息队列
int msgget(key_t key,int flag)
独一无二的key值,flag消息队列的访问权限
返回:成功返回消息队列ID,失败返回-1
接收
int msgrcv(int msgid,void* msgp,size_t size,long msgtype,int flag)
参数
msgid:消息队列ID
msgp:接收消息的缓冲区
size:消息正文字节数
子主题
发送
int msgsnd(int msgpid,const void*msgp,size_t size,int flag)
msgpid:消息队列的ID
msgp:指向消息的指针
struct msgbuf { long type;消息类型 char mtex;消息正文 }
size:消息正文的字节数
返回:成功0,失败-1
删除
信号灯集
支持同时操作多个信号灯,可以避免死锁
死锁:
互斥条件
资源不可剥夺
请求和保存条件
循环等待条件
如何解决死锁
出来互斥不能破坏,其他都可以破坏
服务于共享内存
创建信号灯集
int semget(key_t key,int nsems,int semflg)
和共享差不多
返回:成功semid,失败-1
参数
semid:信号灯ID
key:ftok产生的key值
nsems:信号灯集包含的信号灯数目
semflg:信号灯集的访问权限
IPC_CREAT
IPC_EXCL
0666
初始化信号灯
int semctl(int semid, int semnum,int cmd../*union semun arg*/)
功能:信号灯集合控制
参数
semnum:要操作的集合中的信号灯编号,从0开始
cmd:信号灯集的控制方式
GETVAL:获取信号灯的值,返回值是获得值
SETVAL:设置信号灯的值,要用第四个参数:共用体
IPC_RMID:从系统中删除信号灯集
返回:成功0,失败-1
PV操作
int semop(int semid,struct sembuf*opsptr,size_t nops)
功能:对信号灯集中的信号量进行PV操作
参数
semid:信号灯集ID
opsptr:操作方式,是一个结构体指针
nops:要操作的信号灯的个数
销毁
IPC对象
API接口
套接字
程序
程序就是计算机可识别的指令的集合,静态的,外存
组成
正文段,数据段
线程
进行运算调度的最小单元,包含在进程中,是进程中实际运行的单元,一个进程中可以并发多个线程
共用一片空间(进程的)
pthread三方库
函数thread
线程创建
返回:成功返回0,失败返回错误参数
gcc xxx.c -l pthread
int pthread_create(pthread_t *thread,const pthread_attr_t *attr,(void *)(*start_rtn)(void *),void *arg)
值传递比较好
参数
thread:线程id
attr:属性,默认NULL
start:函数指针,线程的功能函数地址
arg:给线程函数传递的参数,不传就是NULL
线程退出
返回:没有
void pthread_exit(void *retval)
参数
retval:必须是全局变量
线程结束回收资源
返回:成功0,错误返回非0
会阻塞
int.pthread_join(pthread_t thread,void **retval)
参数
thread:线程id
retval:存储错误原因地址
线程分离
int pthread_detach(pthread_t thread)
不会阻塞
进程回收线程
int pthread_cancel(pthread_t pid)
子主题
线程的通信
核心思想:通过全部变量通信
缺点:多个线程相互干扰
互斥
互斥锁mutex
初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutexattr_t *attr)
参数
mutex:指定互斥锁额标识符
attr:互斥锁属性,一般为NULL
返回:成功0失败非0
上锁
int pthread_mutex_lock(pthread_mutex_t *mutex)
判断这个全局变量有没有被其他线程抢占
阻塞函数
参数:mutex是锁的地址
返回:同上
解锁
和上面一样,把lock变成unlock
释放
pthread_mutex_destroy(pthread_mutex_t *mutex)
锁使用完后,在主程序中销毁锁,释放
尝试上锁
int pthread_mutex_trylock(pthread_mutex_t *mutex)
子线程中,除了全局变量相关的工作还要做其他工作
不阻塞
头文件:errno.h
返回
0:成功
EBUSY:被其他线程锁定
非0失败
目的:防止数据竞争和确保数据的一致性,确保在同一时刻只有一个线程或者进程可以访问某个共享资源,包含临界区
锁
实现机制
同步
规定线程的先后顺序,不等于一人一次
生产者V:+1
消费者P:-1
信号量初始化
int sem_init(sem_t *sem,int pshared, unsigned int value)
返回:0成功,-1失败
参数
sem:信号量地址
pshared:0线程使用
value:信号量初值
信号量
概念
任务之间的同步和临界资源的互斥访问
类型
二值信号量
别名(二元信号量,互斥量)
0/1
确保某个时刻只有一个线程可以访问资源
计数信号量
允许多个线程访问一定数量的资源
记录型信号量
整数值和记录进程标识
应用
进程同步
资源管理
解决死锁
生产者-消费者问题
生产
int sem_post(sem_t *sem)
返回:成功0,失败-1
sem:信号量地址
临界区
访问共用资源的程序片段,当共享资源无法同时被多个线程或进程访问,因此当某个线程或者进程进入临界区时,其他线程或者进程必须等待
目的:确保并发执行的线程或者进程能正确地共享数据和资源,避免竞态条件和死锁
互斥锁
条件变量
信号量
事件
实现条件
用户通过APP与系统交互,APP通过shell发送请求,内核解析并执行操作,而驱动则负责将这些操作转化为硬件可以理解的语言,使其工作。这个过程中:硬件是基础。
缓存:临时存储技术,用于提高计算机系统对常用数据或计算结果的访问速度,减少主存RAM,或外存获取数据的时间
CPU从缓存获取数据,如果没有则在内存读取,如果还没有则通过总线从硬盘或其他外设获取,内核是操作系统的主要部分,管理系统资源,缓存分配,CPU负责执行操作系统提供的命令程序
C库函数:由编程语言提供的一组预定义的函数,调用系统调用才能完成任务 操作系统:是计算机系统的核心软件,复制管理计算机硬件和软件资源,为上一个应用层提高稳定,高效的运行环境 系统调用:是操作系统提供给应用程序的一种接口,用于请求操作系统执行某些底层操作
预处理:gcc -E xxx.c -o xxx.i 编译: gcc -S xxx.i -o xxx.s 汇编: gcc -c xxx.s -o xxx.o
环境变量PATH,指定命令的搜索路径,echo $PATH都可以查看环境变量的内容,添加新环境变量:export PATH=$PATH:放我们要添加的路径,(环境变量可以直接在命令行里面使用)
~:用户的家目录,当前用户的主目录 /:Linux是一个文件系统,是文件系统的根目录 /home:是/下的一个子目录,主要存储普通用户信息,用户的家目 /root:是/下的超级用户家目录
return:从函数中返回值给调用者。
return 0:表示函数成功执行完毕,并返回状态码
return -1:表示执行错误
return 1:成功还是错误,取决于程序的设计
exit:立即终止当前程序的执行
exit(0):立即终止当前程序的执行,并表示成功执行完毕
exit(-1):立即终止当前程序,表示程序因错误或异常终止
exit(1):立即结束并报告特定错误
exit(NULL)=exit(0)
子主题
IO并发
提高程序效率,同时操作多个IO任务减少等待时间
多进程
多线程
实现方法
线程池:管理多个线程,避免频繁的创建和销毁线程带来的开销
优化IO操作,采用非阻塞IO或者异步IO
时间片轮转
Linux并发控制
解决多任务并行执行时资源竞争问题
本质:是操作系统和驱动程序用于管理多个并发执行单元,对共享资源访问的机制,是实现IO并发的基础
控制机制分类
原子操作类
在执行过程中不会被别的代码路径中断
异常可用
忙等待类
不断检查共享资源的状态
中断可用
自旋锁
保护临界区,不释放CPU
信号量
PV操作实现
互斥锁
PV
保护临界区,释放CPU
子主题
阻塞类
资源可用时,才会获取共享资源
任务可用户
例子:信号量,互斥锁
死锁:2个或2个以上的进程线程互相等待对方释放资源
Linux的IO模型
分类
阻塞IO
进程在发起IO操作后会阻塞等待IO操作完成
非阻塞IO
进程在发起IO操作后,会立即返回继续执行其他任务
多路复用IO
select,poll,epoll同时监控多个文件描述符
异步IO
进程在发起IO操作后,可以立即返回继续执行其他服务,并在IO操作完成时通过回调函数或信号通知进程
开发调试工具
Vim编辑器编写源代码
命令模式
插入模式
末行模式
GCC编译器将源代码文件编译成可执行文件
ESC
ISO
GDB调试器对可执行文件进行调试,设置断点,查看变量,单步执行
运行:run
设置断点:break 行号
单步执行:step
Git:对代码进行版本控制,管理代码的更改和提交历史,开发人员可以进行本地代码更改, 并轻松将这些更改推送到远程仓库,或者从远程仓库获取最新的代码更改
初始化Git仓库:git init
提交改动:git commit
Makefile:构建工具,指导编译器执行编译和链接过程,
make可以自动化编译,可定制,可扩展
shell:自动执行操作系统的命令和任务的脚本语言,解释性语言,
Android系统架构
内核
内存管理,驱动模块
HAL(硬件抽象)
提高标准化接口,以便操作系统和应用程序能访问硬件
系统运行需要的库
C库
应用程序框架层Framework
程序和底层交互的接口
应用程序层
所有安装在设备上的应用程序
设备驱动是操作系统和硬件设备的桥梁
I2C驱动架构
组成
I2C核心:提高总线驱动和设备驱动的注册和注销方法
I2C总线驱动:一般集成在CPU内部,包含适配器的数据结构通道
I2C设备驱动
USB驱动架构
主机控制器驱动
设备驱动
LCD驱动架构:帧缓冲,来控制LCD显示
显示设备驱动
Platform驱动架构
都是虚拟的,管理没有实际总线的外设,使用match函数来匹配
当一个设备被注册时,Platfrom总线会遍历所有的已经注册的驱动,调用match匹配
Input子系统驱动架构
处理输入设备的架构
Flash驱动架构
引导加载,操作系统镜像
MTD驱动架构
通用驱动架构:总线驱动,设备驱动
驱动架构层次:
总线层:最底层,负责控制和管理各种总线上的数据传输和设备通信
设备层:总线之上,针对具体硬件设备写的驱动
核心层: