第1节
1、标准IO的介绍
2、缓冲区
3、相关函数
1、标准IO的介绍
标准IO:标准C库提供的一系列用来进行输入和输出的函数
输入:将数据从文件拷贝到内存
(标准输入(scanf):从键盘将数据拷贝到内存)
输出:将数据从内存拷贝到文件
(标准输出(printf):将数据从内存拷贝到终端)
FILE结构体: 每个被使用的文件都在内存中开辟一个区域,用来存放文件的有关信息,
这些信息是保存在一个结构体类型的变量中,该结构体类型是由系统定义的,取名为FILE。
缓冲区:两块内存空间(读缓冲区和写缓冲区),只要打开文件,系统就会开辟对应的空间
思考:缓冲区的作用?为什么需要?
可以减少系统调用的次数,减少系统的开销
流:内存和缓冲区之间字节的进进出出,将该现象形象成为流
写文件实际上可以理解为:将内存中的数据放入流中,然后到达缓冲区,最终到达文件
所以:对流的操作就是对文件的操作
文件是用FILE指针来操作的—》流也可以用FILE指针来操作
2、缓冲区:用何时刷新缓冲区对其进行分类,分为以下三类:
全缓冲区:当缓冲区满、强制刷新或者程序结束就会刷新缓冲区
行缓冲区:当缓冲区满、强制刷新、程序结束或者遇到换行符就会刷新缓冲区
不缓存:没有缓冲区
举例:printf–行缓存
注意:程序运行,系统就会自动打开三个流,stdin stdout stderr
3、相关函数
打开一个流(文件):fopen
读文件:fread(按块读文件) fgetc(按字符读) fgets(按行读)
写文件:fwrite fputc fputs
关闭文件:fclose
FILE *fopen(const char *path, const char *mode);
功能:打开一个流
参数:path–文件的路径
mode–打开文件的方式
返回值:成功 文件流指针
失败 NULL
int fgetc(FILE *stream);
功能:从指定的流中读一个字符
参数:stream–指定的流(可读的打开的流)
返回值:成功 从流中读到的字符的ASCII码值
返回值 -1
int feof(FILE *stream);
功能:判断文件是否结束
注意:当你先读到文件结束标志以后,才能使用feof来判断
返回值:0 文件没有到达末尾
非0 文件到达末尾
fgets与gets的区别:
1、gets只能从标准输入读数据,但是fgets可以从任何文件流中读数据
2、gets不做越界检查,但是fgets会进行越界检查
3、gets不会将’\n’读入buf,但是fgets会
char *fgets(char *s, int size, FILE *stream);
功能:从指定的流中读一行,遇到’\n’就结束
注意:如果一行比size小,就读一行,’\n’也读了
如果一行比size大,就读size-1个,因为最后一个是’\0’
参数:s—内存空间的首地址
size—内存空间的大小
stream—指定的文件流
返回值:成功 内存空间的首地址
文件到达末尾 NULL
失败 NULL
puts和fputs的区别:
1、puts输出时会自动加’\n’,而fputs不加
2、puts只能输出到标准输出流中,但是fputs可以输出指定的任意文件流中
以上:操作的都是字符或者字符串,只能对文本文件进行操作
但是:实际文件既有文本文件又有二进制文件,此时就要用fread和fwrite了
按块读写文件:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE stream);
参数:ptr—内存空间的首地址(可以是任何类型)
size—每一块的大小
nmemb–块数 sizenmemb = 内存大小
stream–文件流
返回值:失败 -1
成功 实际读到的块数
size_t fwrite(const void *ptr, size_t size, size_t nmemb,
FILE *stream);
参数:ptr—内存空间的首地址(可以是任何类型)
size—每一块的大小
nmemb–块数 size*nmemb = 内存大小
stream–文件流
返回值:失败 -1
成功 实际写入的块数
fseek:可以改变文件的当前读写位置
ftell:可以获得文件的当前读写位置,一般可以用来获得文件大小
第2节
1、文件IO
2、stat函数
3、目录操作
4、静态库和动态库的制作
1、文件IO
文件IO:linux操作系统提供的一系列用来进行输入和输出的函数
操作文件:文件描述符(非负整型数)–只要打开文件,内核就会给该文件分配一个编号
相关函数:open read write close
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
功能:打开或创建文件
如果文件存在,则打开文件,用两个参数的open
如果文件不存在,则创建并打开文件,用三个参数的open,第三个参数用来指明新创建的
文件的存取权限,一般设置为0666
参数:pathname–文件路径
flags—打开方式 只读:O_RDONLY 只写:O_WRONLY
可读可写:O_RDWR
如果需要创建文件:| O_CREAT
追加:| O_APPEND
清空文件:| O_TRUNC
测试文件是否存在:| O_EXCL
返回值:成功 非负整数(最小未用的文件描述符)
失败 -1
umask:文件权限掩码–当(touch)新创建一个文件时,会将0666 & ~(umask)–》文件权限
新建文件夹:0777 & ~umask --》0755(因为如果文件夹没有可执行权限,就进不去)
perror原理:当函数发生错误时,系统会去改全局变量errno的值,当执行perror时,会去查看这个
errno的值,每一个值都已经对应了一个错误原因,所以perror会根据errno的值打印
对应的错误信息
注意:errno在errno.h中定义
char *strerror(int errnum);–通过errno的值获得错误信息
返回值:是errno对应的错误信息
使用:printf("%s\n", strerror(errno));
ssize_t read(int fd, void *buf, size_t count);
函数功能:读文件
参数:fd–文件描述符
buf:内存空间首地址
count:要读的字节数 (一般就是内存空间的大小)
返回值:成功 实际读到的字节数
失败 -1
ssize_t write(int fd, const void *buf, size_t count);
函数功能:写文件
参数:fd–文件描述符
buf:内存空间首地址
count:要写的字节数
返回值:成功 实际写入的字节数
失败 -1
总结:文件IO和标准IO的区别?
1、标准IO是c库提供的,文件IO是linux系统提供的
2、标准IO是通过FILE*来操作文件,文件IO是通过文件描述符操作文件
3、标准IO有缓冲区,系统开销小,文件IO没有缓冲区,系统开销大
4、标准IO可移植,而文件IO不行
5、标准IO只能操作普通文件,而文件IO可以操作普通文件、设备文件、管道文件等等
lseek:既可以改变文件当前读写位置,又可以获得当前位置
lseek:只读普通文件有效,不引起任何IO操作
空洞文件的用途:可以提前占有磁盘空间
2、stat函数–可以获得一个文件的详细属性
int stat(const char *path, struct stat *buf);
参数:path–文件路径
buf–所指向的内存空间是存获得的文件信息的
给buf传参时,需要一个结构体变量的地址
实际上就是stat这个函数执行时,给这个变量
赋值
返回值:0 成功
-1 失败
st_mode 33188 001000000110100100
0170000 001111000000000000
普通文件 0100000 001000000000000000
目录 0040000 000100000000000000
字符设备 0020000 000010000000000000
套接字 0140000 001100000000000000
0120000 001010000000000000
0060000 000110000000000000
0010000 000001000000000000
用户可读 00400 000000 000100000000
3、目录操作
openDir readDir closeDir
DIR *opendir(const char *name);
功能:打开目录
参数:name–目录路径
返回值:成功 目录流指针 失败 NULL
struct dirent *readdir(DIR *dirp);
功能:读目录
参数:dirp openDir的返回值
返回值:NULL 读完了 因为一次只读一个文件
读到的文件的信息
4、静态库和动态库的制作
库:实际上就是一种代码的二进制形式
静态库和动态库的区别:
1、静态库是在编译阶段,就链接到目标代码中,生成的文件体积较大,但是可移植性好
2、动态库是在运行阶段,链接到目标代码中,生成的文件体积较小,但是如果移植,需要
将库一起移植
库的后缀:动态库:.so 静态库:.a
前缀:lib 中间的是库名 例如:libfun.so fun是库名
gcc编译时:-L–》指定库所在路径 -l–》库名
如果将库放到/lib或者/usr/lib目录下,就可以不用指定库的路径,系统会自动到这两个目录下找
注意:如果使用的是动态库,那么在运行时,链接器就会到系统指定的路径下找对应的库,也就意味着
动态库必须在系统指定的路径下,有两种方法:
1、动态库必须在/lib或者/usr/lib目录下
2、改变ld的配置文件,告诉系统也可以到你指定的路径下找库,如何改:
sudo vim /ect/ld.so.conf.d/my.conf
添加路径
刷新:ldconfig
第3节
进程
1、进程的相关概念
2、进程的系统调用
3、exec函数族
1、进程的相关概念
概念
进程:程序的一次动态执行过程
进程和程序的区别:
1、程序是静态的,进程是动态的
2、程序是可以存储在外存上,而进程不能存储
虚拟内存0-4G:是操作系统提供的—和物理内存存在映射关系
每一个进程都有一个0-4G的虚拟内存空间–也就是产生一个进程,系统就会给这个进程分配0-4G的虚拟内存空间
总结:进程是程序执行和资源管理的最小单位
系统如何管理进程?
通过PCB,其实就是一个task_struct的结构体(进程产生,系统就会产生task_struct的结构体,存放进程的相关信息,
用来管理进程)
进程的类型
1、交互进程 2、批处理进程 3、守护进程
守护进程特点:
1、在后台执行,与终端无关
2、系统启动时开始运行,系统关闭时,终止运行
进程的状态
1、运行态(正在运行或准备运行)
2、等待态(等待资源,可中断和不可中断)
3、停止态(暂停运行)
4、僵死态(运行结束,但task_struct没有被释放)
进程的模式
1、用户模式 2、内核模式
2、进程的系统调用
前提:
1、进程都是被进程创建出来的
2、用户空间的第一个进程是由内核创建出来的,叫init进程
原来的进程叫做父进程,新创建的进程就是子进程
子进程创建成功后,也会有0-4G的内存空间,会把父进程的内存空间的内容进行拷贝
子进程几乎是父进程的完全拷贝
pid_t fork(void);
功能:创建子进程
返回值:-1 失败
>0 父进程(子进程的pid)
==0 子进程
说明:fork返回之前,子进程就创建成功,子进程接下来要和父进程继续执行父进程没有执行的代码
第一条语句就是fork返回,所以用fork的返回值来区分父子进程
孤儿进程:父进程先于子进程退出,此时子进程就会被init进程收养,此时的子进程就称为孤儿进程
当一个进程结束时,需要它的父进程去回收该进程的退出状态(task_struct结构体),如果没被回收,
此时该进程就会成为僵尸进程
僵尸进程:子进程运行结束,但是父进程没有结束,也不去回收子进程的退出状态,此时子进程就称为
僵尸进程,但是如果父进程结束,系统就会回收子进程的退出状态
》进程的调度顺序是不确定的,这个与系统的调度策略有关,没有必须谁先谁后
exit和_exit
功能:结束进程
区别:
exit:清空缓冲区
_exit:不会清空缓冲区
return:结束函数,当return在main函数中时,表示结束进程
3、exec函数族
如果某一个进程想调用一个可执行文件,原有进程内存的内容被新的可执行文件替代,也就是
在原有的进程中启动一个可执行文件的执行,此时就要使用exec函数族来实现
exec函数族的函数作用是类似的,只不过是传参方式不同而已
execl
execv
execle
execve
execlp
execvp
l:参数需要一个一个列举出来
v:将参数可以放到一个指针数组中
e:将环境变量也传进去
p:会到PATH指定的路径下去查找可执行文件,也就是可以不用指明路径
第4节
1、守护进程
2、线程
1、守护进程
特点:
1、在后台运行,与终端无关
2、在系统启动时运行,在系统关闭时终止
想清楚:现在与终端有什么关系?
1、血缘关系
2、有控制终端
思考:怎么与终端无关?
1、断绝血缘关系 孤儿进程
2、没有控制终端 创建新的会话组
进程组:多个进程
会话组:多个进程组(属于同一个控制终端的进程在一个会话组)
创建守护进程的步骤:
1、创建子进程,父进程退出 — 目的:与终端断绝血缘关系
2、创建新的会话组 — 目的:彻底让子进程与终端无关
//守护进程创建成功
3、改变工作目录为"/tmp" --目的:防止工作目录被删除
4、设置文件权限掩码为0 --目的:使守护进程更加灵活,不受系统中umask值的影响
5、关闭文件描述符 --目的:关掉所有用不到的文件
2、线程:轻量级的进程
每一个进程都有0-4G的内存空间,所以多进程之间来回切换时,存在地址空间切换的问题,系统开销
比较大
能不能有这样一个东西,任务之间不用地址空间的切换,而且还不影响多进程下,多任务的功能,有
了线程的概念
多个线程是共享一个进程的地址空间,对于操作系统而言,进程和线程没有区别,参与统一调度,也是
用task_struct结构体来管理线程的,但一个同一个进程创建出来的线程共享这个进程的地址空间
进程是程序执行和资源管理的最小单位
线程是系统调度的最小单位
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
功能:在进程中创建子线程(原来的进程可以称为主线程)
参数:thread–创建成功的线程的ID号
attr–线程的属性,一般设置为NULL
start_routine-指向线程处理函数的指针
arg–传给线程处理函数的参数
线程处理函数:子线程的任务就是在这个函数中完成的
该函数我们只需要传给pthread_create函数就好,只要线程创建成功,该函数就会被调用
线程就会参与系统的调度
返回值:成功 0
失败 -1
int pthread_join(pthread_t thread, void **retval);
功能:主线程等待并回收子线程的退出状态(task_struct结构体)
参数:thread–要等待的线程的id号
retval–子线程的返回值
返回值:成功 0 失败 -1
将线程设置为游离态,当线程结束时,系统会自动回收线程退出状态
线程系统开销小,但是多个线程共享地址空间,就会出现资源竞争的问题,解决方法:互斥锁
互斥锁能够保证:一个线程在使用共享资源时数据的完整性,但是不保证执行顺序,系统先调度谁
谁就先执行
mutex_init:得到锁,对锁初始化
mutex_lock:上锁
mutex_unlock:解锁
第5节
进程间通信总结
通信方式
(1)无名管道
(2)有名管道
(3)信号
(4)共享内存
(5)消息队列
(6)信号量
注意:以上都是同一台主机的进程间通信。
由数据交换的通信方式:无名管道 有名管道 共享内存 消息队列
信号:一种异步通信机制
信号量:同步机制
(1)无名信号量:多用于线程间
(2)有名信号量:进程间
不同主机间的通信方式:只有套接字