IO:input and output
I/O:输入/输出
回顾:计算机由几部分构成
处理器,输入/输出设备,存储器,总线
处理器:运算器、控制器、寄存器
存储器:内存、外存
输入输出设备:
输入设备:键盘,鼠标,MIC,摄像头等
输出设备:显示器,喇叭等
总线:
地址总线
数据总线
哈弗结构:数据和指令使用不同的总线传输,不需要编解码,效率高
冯诺依曼结构:数据和指令使用相同的总线传输,需要复杂的编解码方法,效率低
Linux下:用户如何操作磁盘文件,想要读取指定的文件内容

Linux Kernel的结构和构成:
内存管理单元(MMU):
内存分配和管理
-》虚拟地址
-》线性地址
-》物理地址
2. 进程管理单元
APP
程序——》“任务”(单进程、多进程、单进程多进程)
死的 活的
3. 文件系统
——》文件系统是操作系统提供用户和计算机交互的结构
——》组织管理文件
4. 网络模块
提供网络协议栈等功能
5. 驱动模块
提供设备驱动
Linux文件系统的结构:


2. 系统调用
2.1 什么是系统调用
操作系统留给用户操作计算机稀有资源的接口(一系列函数)
2.2 为什么要使用系统调用函数
保护稀有资源
提供用户访问稀有资源的接口
3. 防止用户访问稀有资源的竞态导致稀有资源被破坏
2.3 哪些都是系统调用
文件IO相关函数
进程线程中学的绝大多数函数都是
网络编程中学的绝大多数函数都是
2.4 系统调用的实现过程

上述过程牵连几个问题:
中断上下文的切换
2. 函数可重入性的问题
从上图可知,系统调用很耗时
补充:
中断上下文切换的过程
——》发起中断请求
——》保存上文(用户态的运行状态)
——》切换下文(加载Kernel的指令)
——》Kernel执行
2. 函数可重入性
当函数执行过程中被中断,当中断结束后,函数是否能按照之前的状态接着运行,这样的问题,叫做可重入问题。
如果可以,称为可重入函数,该函数具备可重入性。
3. 所有的系统调用都是可重入的。
2.5 如何解决系统调用耗资源的问题
——》系统调用本身是没办法优化额
——》但是可以减少系统调用的次数
——》次数减少,但是活不能少干
所以引入缓冲机制,建立用户态缓冲区,进而减少系统调用的次数
3. IO的分类
根据封装来源不同,分为标准IO和文件IO
标准IO:标准C库提供的IO操作接口,称为标准IO
文件IO:类UNIX操作系统中,操作系统提供的POSIX系统调用(IO操作)的接口。
POSIX:可移植操作系统(类UNIX的代称)接口
3.1文件IO和标准IO的区别

4. 标准IO
4.1 标准IO的特点
因为有缓冲机制, 减少了系统调用的次数,进而缩小的系统开销。
4.1.2 有缓冲机制
4.1.2.1 如何验证缓冲区的存在
使用_exit()函数,结束程序,不会清空IO的缓冲区
4.1.2.2缓冲区存在,但是有几种类型
全缓冲:其余的默认全缓冲
行缓冲:stdin、stdout
无缓冲:stderr
注意:每个进程都有自己的stdin、stdout、stderr
4.1.2.3如何刷新缓冲区
缓冲区满时,自动刷新缓冲区
调用exit()函数程序退出
函数return的时候
调用fclose
调用fflush
对于行缓冲,遇到'\n'会刷新
4.1.2.4缓冲区的大小是多少,能不能该,如何改
行缓冲:1024Bytes
全缓冲的大小:4096Bytes
标准IO缓冲区的大小是可以修改的,通过setvbuf可以修改
5. 标准IO如何操作文件的
标准IO通过一个文件流指针的对象来操作文件
文件流:file stream/stdio stream
标准IO提供了三种较为特殊的文件流:
stdin:标准输入流(终端输入)
stdout:标准输出流(终端输出)
stderr:标准错误(程序运行的错误信息)
5.1 如何获取文件流指针
我们使用fopen函数来获取文件流指针
5.2 如何释放文件流指针
我们使用fclose函数来释放文件流指针
5.3 如何操作文件流指针
——》从文件流中读取数据
fread、fputs
——》往文件流中写入数据
fwrite、fgets
——》文件流指针的重定位
fseek、ftell
5.4 实现一个cat命令
./mycat file
通过main函数传参,获取想要读取的文件名
int main(int argc,char *argv[])
{
return 0;
}
这样写有个功能:通过执行指令可以传递参数,参数用空格隔开。
参数的类型都是字符串类型
例如:./calc 1+3 =
指令 参数1 参数2 参数3 参数 4
“./calc” "1" "+" "#" "="
argc:int,说的是命令行参数的个数,包括指令在内。
char *argv【】:
argv:是一个数组,数组的每个元素都是char *类型的。
数组中存储的是每一个参数的首地址。
argv[0]:指令
argv[1]:参数1
char *:
char类型变量的指针
char b = 15;
char *p = &b;
2. char类型数组的首地址
char arr【】 = {1,2,3,4,5};
char *p = arr;
int i = 0;
for(i = 0;i< sizeof(arr)/sizeof(arr[0]);i++)
{
printf("%hhd",p[i]);
}
注意:要给指针一个操作范围。
3. 字符串的首地址
char str[] = "mytest";
char *p = str;
printf("%s\n",p);
2. 打开
3. 读并输出
4. 关闭
5.5获取本地时间,并将本地时间写入一个指定的文件
main函数传参获取文件名
打开文件(w)
获取本地时间
time(time_t *ptim):获取1970:1:1:0:0:0到现在的秒数
char *pstr = ctime(ptim);
fwrite
fclose
5.6获取指定文件的大小
以只读形式打开指定文件
fseek跳转到文件末尾
ftell获取文件流指针的偏移量
fclose
函数接口:
/*需要包含的头文件*/
#include <unistd.h>
/*
*函数名:_exit
*函数功能:退出/终止正在执行的进程,不会flush标准的流(不会清空标准IO的缓冲区)
*函数参数:
*int status:表示退出的状态
*函数返回值:void
*/
void _exit(int status);
fopen函数:
/*需要包含的头文件*/
#include <stdio.h>
/*
*函数名:fopen
*函数功能:打开一个文件,获取文件的文件流指针
*函数参数:
* const char *pathname:带路径的文件名
* const char *mode:打开方式

*函数返回值:FILE *:成功返回文件流指针,失败返回NULL。
*/
FILE *fopen(const char *pathname,const char *mode);
fclose函数:
/*需要包含的头文件*/
#include <stdio.h>
/*
*函数名:fclose
*函数功能:关闭文件描述符,并刷新缓冲区
*函数参数:FILE *stream:文件流指针
*函数返回值:int:成功0,失败返回EOF(-1)和错误码
*/
int fclose(FILE *stream);
fread函数
/*需要包含的头文件*/
#include <stdio.h>
/*
*函数名:fread
*函数功能:从文件流指针中读取数据
*函数参数:
*void *ptr:存储读取来的数据的内存首地址
*size_t size:每一个读取单元的大小,单位是字节
*size_t nmemb:想要读取的单元个数
*FILE *stream:文件流指针
*函数返回值:size_t:成功返回读到的单元个数,失败返回short nmemb count,或者是0
*/
size_t fread(void *ptr,size_t size,size_t nmemb, FILE *stream);
注意:想要读取的字节数:nmemb *size,当size == 1时,表示nmemb就是想要读取的字节数
/*
函数名:fwrite
*函数功能:往文件流指针中写入数据
*函数参数:
*void *ptr:存储想要写入的数据的内存首地址
*size_t size:每一个写入单元的大小,单位是字节
*size_t nmemb:想要写入的单元个数
*FILE *stream:文件流指针
*函数返回值:size_t:成功返回写入的单元个数,失败返回short nmemb count,或者是0
*/
size_t fwrite(const void *ptr,size_t size,size_t nmemb,FILE *stream);
time函数:
/*需要包含的头文件*/
#include <time.h>
/*
*函数名:time
*函数功能:获取时间单位为秒(即从1970-01-01 00:00:00 +0000(UTC)到现在的秒数)
*函数参数:time_t *tloc:可以给NULL,可以给一个变量的指针。
*给NULL,不影响什么
*给了一个time_t变量的地址,会将获取来的秒数存入该变量。
*函数返回值:time_t:成功返回秒数,失败返回(time_t)-1。
*/
time_t time(time_t *tloc);
ctime函数:
/*需要包含的头文件*/
#include <time.h>
/*
*函数名:ctime
*函数功能:将秒数转换为字符串类型的时间
*函数参数:const time_t *timep:指向time_t类型的指针
*函数返回值:char *:字符串首地址,失败返回NULL
*/
char *ctime(const time_t *timep);
fseek函数:
/*需要包含的头文件*/
#include <stdio.h>
/*
*函数名:fseek
*函数功能:修改文件流指针指向的位置
*函数的参数:
*FILE *stream:文件流指针
*long offset:偏移量
*int whence:基准
*函数返回值:int:成功返回0,失败返回-1
*/
int fseek(FILE *stream,long offset,int whence);

ftell函数:
/*需要包含的头文件*/
#include <stdio.h>
/*
*函数名:ftell
*函数功能:获取当前文件流指针的位置
*函数参数:
*FILE *stream:文件流指针
*函数返回值:long:成功返回偏移量(当前位置,到文件开头的偏移量),失败返回-1
*/
long ftell(FILE *stream);
day2
文件IO
文件IO是类UNIX操作系统提供的POSIX系统调用。
文件IO的操作对象:文件描述符
文件描述符:是一个≥0的整数,可以通过这个数来操作文件
0:stdin
1:stdout
2:stderr
3.1针对文件描述符展开的操作
——》如何获取文件描述符
open
creat(不用)
——》如何关闭文件描述符
close
——》如何从文件描述符中读取数据
read
——》如何往文件描述符中写入数据
write
——文件读写位置重定位
lseek
3.2文件权限掩码
mask:为文件权限掩码,进程可以通过umask函数修改文件权限掩码。默认文件权限掩码为0002
creat(),open(),mkdir()都会用到mask,即创建文件,文件夹时都有文件权限掩码。
文件权限掩码的使用:
最终的文件权限= ~文件权限掩码&文件权限(mode)
例如:mode:777
最终得到的权限:0777& ~0002
111 111 111 777
000 000 010 002
111 111 101 775(~002)
4. 库
4.1 如何实现代码复用
封装函数接口(API)就能够实现代码复用,但是,此时还需要源文件的拷贝,然后联编。
4.2 如何实现代码复用,且不公开源代码
库可以实现不公开源代码,但是能复用代码。
4.3如何实现代码复用和拼接
没有库的情况下,是可以使用目标文件来实现代码共享的。
4.4 代码重定向
代码的拼接就是代码重定向
4.5可重定向文件(ELF文件)
链接的过程就是做代码拼接
目标文件
库
可执行文件
4.6gcc编译流程中的链接
——》预处理
处理预处理指令的
#include
#ifndef
#define
#ifdef
#if
#endif
#else
#elif
条件编译:
#ifdef:宏名
代码段
#endif
编译的时候:gcc xxx.c -D宏名
屏蔽代码:
#if 0
被屏蔽的代码
#endif
——》编译
将.c/.i文件转为汇编命令
——》汇编
.c/.i/.s 汇编为二进制指令(生成的文件.o目标文件)
——》链接
链接目标文件或者库
gcc xxx.c/.o -l库名 -L库的路径
4.7 什么是库
库,是编译好的,不可执行的,二进制代码,专门用来拼接的代码。
4.8库的分类
我们根据库的链接阶段不同分为静态库和动态库。
链接阶段:
编译时链接——》静态链接——》使用静态库
运行时链接——》动态链接——》动态库
静态库:是编译时链接的库,一般在Linux下,以“.a”为文件后缀
动态库:也叫共享库,是运行时链接的库,一般在Linux下,以".so"为文件后缀
在windows下,静态库以".lib"为后缀,动态库以“.dll”为后缀。
4.9静态库和动态库的区别

4.10 库的制作
4.10.1 Linux下创建与使用静态库
Linux静态库命名规则
Linux静态库命名规范,必须是“lib[you library name].a”:lib为前缀,中间是静态库的名字,扩展名为.a。
创建静态库(.a)
通过上面的流程可以知道,Linux创建静态库过程如下:
首先将代码文件编译成目标文件.o(MYSORT.o)
gcc -c mysort.c MYSORT.o(注意带参数-c,否则直接编译为可执行文件)
通过ar工具将目标文件打包成.a静态库文件
ar -crv libmysort.a MYSORT.o
生成静态库libmysort.a
4.10.2 Linux下创建与使用动态库
创建动态库(.so)
1. 编写代码
2. 将代表生成目标文件,此时要加上编译器选项-fPIC
-fPIC创建与地址无关的编译程序(pic, position independent code),
是为了能够在多个应用程序间共享。
gcc -fPIC -c mysort.c
3. 然后生成动态库,此时要加链接器选项 -shared
gcc -shared -o libmysort.so mysort.o
-shared指定生成动态链接库。
注意:上述两个过程也可以合二为一
gcc -shared -fPIC -c mysort.c -o libmysort.so
4.11 库的链接
gcc xxx.c -L+库所在的路径 -l库名(不要前面的lib和后面的.so)
静态库的搜索路径(-L选项),指定静态库名(不需要lib前缀和.a后缀,-l选项)。
-L:表示要链接库所在目录
-l:制定链接时需要的库,编译器查找链接时有隐含的命名规则,即在给出名字前面加上lib,后面加上.a或.so来确定库的名称。
文件夹操作
创建文件夹
#include <sys/stat.h>
#include <sys/types.h>
int mkdir(const char *pathname,mode_t mode);
删除空文件夹
#include <unistd.h>
int rmdir(const char *pathname);
获取文件夹的信息
#include <dirent.h>
struct dirent *readdir(DIR *dirp);
struct dirent
{
ino_t d_ino; /*Inode number*/
off_t d_off /*Not an offset;see below*/
unsigned short d_reclen; /*Length of this recor*/
unsigned char d_type; /*Type of file; not supported
by all filesystem types */
char d_name[256]; /*Null-terminated filenamed*/
};
d_type:8种类型,见manual手册
打开文件夹
#include <sys/types.h>
#include <dirent.h>
DIR *opendir(const char *name);
关闭文件夹
#include <sys/types.h>
#include <dirent.h>
int closedir(DIR *dirp);
文件夹操作案例
#include <stdio.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int readFileList(char *path);
int main(int argc, char *argv[])
{
readFileList(argv[1];
return 0;
}
int readFileList(char *path)
{
if(NULL == path)
{
puts("this path is no exist");
return -1;
}
DIR *dir;
struct dirent *ptr;
char base[1000];
dir = opendir(path);
if(NULL == dir)
{
puts("opendir error.");
return -2;
}
while((ptr = readdir(dir)) != NULL)
{


函数接口
feof函数:
/*需要包含的头文件*/
*函数名:feof
*函数功能:检测文件流指针是否指向文件末尾
*函数参数:FILE *stream:文件流指针
*函数返回值:int:如果到文件末尾返回值非0(真),没到,返回0(假)
int feof(FILE *stream);
fflush函数:
/*需要包含的头文件*/
*函数名:fflush
*函数功能:刷新文件流指针的缓冲区
*函数参数:FILE *stream:文件流指针,如果为NULL,清空所有打开的文件流指针的缓冲区
*函数返回值:int:成功返回0,失败返回EOF(-1)和错误码。
int fflush(FILE *stream);
open函数:
/*需要包含的头文件*/
#Include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*
*函数名:open
*函数功能:打开文件(文件存在的情况下)
*函数参数:
const char *pathname:带路径的文件名
int flags:打开的方式
*函数返回值:int:成功返回打开的文件的文件描述符,失败返回-1和错误码
*/
int open(const char *pathname, int flags);
/*
*函数名:open
*函数功能:打开文件(文件不存在的情况下可以创建)
*函数参数:
const char *pathname:带路径的文件名
int flags:打开的方式
mode_t mode:文件权限
*函数返回值:int:成功返回打开的文件的文件描述符,失败返回-1和错误码
*/
int open(const char *pathname,int flags,mode_t mode);
flags:是一组flag
O_RDONLY:只读
O_WRONLY:只写
O_RDWR:可读可写
O_CREAT:文件不存在创建
O_TRUNCC:截断
O_NOCTTY:摆脱终端,Linux下串口通信
O_APPEND:追加
O_NONBLOCK or O_NDELAY:非阻塞

mode_t: 8进制的文件权限
例如:0777 0664
close函数:
/*需要包含的头文件*/
#include <unistd.h>
/*
*函数名:close
*函数功能:关闭文件描述符
*函数参数:int fd :文件描述符
*函数返回值:int:成功返回0,失败返回-1和错误码
*/
int close(int fd);
read函数:
/*需要包含的头文件*/
#include <unistd.h>
/*
*函数名:read
*函数功能:从文件描述符中读取数据
*函数参数:
*int fd:文件描述符
*void *buf:存放读到数据的内存首地址
*size_t count:想要读取的字节数
*函数返回值:int:成功返回读到的字节数,失败返回-1和错误码,当返回值为0,表示读到文件末尾
*/
ssize_t read(int fd,void *buf,size_t count);
write函数:
/*需要包含的头文件*/
#include <unistd.h>
/*
*函数名:write
*函数功能:往文件描述符中写入数据
*函数参数:
*int fd:文件描述符
*void *buf:存放想要写入数据的内存首地址
*size_t count:想要写入的字节数
*函数返回值:int:成功返回写入的字节数,失败返回-1和错误码
*/
ssize_t write(int fd,const void *buf,size_t count);
lseek函数:
/*需要包含的头文件*/
#include <sys/types.h>
#include <unistd.h>
/*
*函数名:lseek
*函数功能:文件读写指针重定位
*函数参数:
*int fd:文件描述符
*off_t offset:相对于基准的偏移量
*int whence:基准(SEEK_SET,SEEK_CUR,SEEK_END)
*函数返回值:int:成功返回文件头移动后位置的偏移量,失败返回-1和错误码
*/
off_t lseek(int fd,off_t offset,int whence);