统一声明:
博客转载 声 明 : 本博客部分内容来源于网络、书籍、及各类手册。
内容宗旨为方便查询、总结备份、开源分享。
部分转载内容均有注明出处,如有侵权请联系博客告知并删除,谢谢!
百度云盘提取码:统一提取码:ziyu
个人网站入口:http://www.baiziqing.cn/
一、标准IO
1.1、定义:
以 ANSIC为标准,在系统调用之上封装的接口库,
为IO操作底层的系统调用提供一个通用接口。
1.2、文件指针:
FILE指针,每一个被操作的文件都在内存中开辟一个区域,
用来存储文件的相关信息,该空间的类型为结构体类型,名字为FILE。
流定义:所有的IO操作仅是简单的从程序移进或者移出,这种字节流,被称为流。
所谓的流也被称为FILE指针
1.3、缓存类型:
1、全缓存:当IO缓存区满了或者满足一定条件,就会刷新缓存区 占4k 4096
2、行缓存:只有当遇见'\n'时,才刷新缓存区 占1k 1024
3、不缓存:标准错误输出,stderr
1.4、刷新缓存:
int fflush(FILE *strem);
1.5、更改缓存类型
void setbuf(FILE *strem, char *buf);
将 stream 流的缓存改成 用户自定义的 buf 缓存
1.6、linux中打开一个终端时,系统默认打开三个文件:
标识符
标准输入 stdin 0
标准输出 stdout 1
标准错误输出 stderr 2
1.7、文件操作流程:
1.7.1、打开(创建打开)
FILE *fopen(const char * path, const char *mode);
功能:
打开(创建打开)
参数:
path ---> 待操作的文件名(可包含路径)
mode ---> 文件打开方式(只读r、只写w、追加a、可读写r/w/a+)
返回值:
成功: 文件指针
失败: NULL ,并设置errno
1.7.2、数据读写
(1)、将数据format 格式输出到 指定的stream流中
int fprintf(FILE *stream, const char *format, ....)
扩展:
1、将数据按照 format 格式输出到 buf 地址上
int sprintf(char *buf, const char *format, ....)
2、将 buf 地址上的数据,按照 format 格式,赋值给后面的变量
int sscanf(const char *buf, const char *format, ....)
(2)、 一次操作一个字符
int fgetc(FILE *stream);
功能:
从指定的 stream 流中读取一个字符 出来
参数:
stream: 要指定的流
返回值:
成功: 返回写一个字符。
失败: EOF
文件末尾:EOF
int fputc(int c, FILE *stream)
功能:
将 c 中的数据 写入指定的 stream 流中
参数:
c --> 要写入的字符数据
stream --> 指定的流
返回值:
成功: 非负值
失败: EOF
(3)、一次操作一行数据
char *fgets(char *buf, size_t size, FILE *stream)
功能:
从指定 stream 流的一行中,读取前 size 个字节的数据到 buf 地址上。
参数:
buf --> 存储读取出来数据的缓存区首地址
size --> 要读取一行的字节数
stream --> 指定的流
返回值:
成功: 返回 buf
失败: 返回 NULL
文件末尾: 返回 NULL
int fputs(char *buf, FILE *stream)
功能:
将buf 地址上的数据 写入指定的 stream 流中,遇见'\n'结束
参数:
buf --> 写入数据的来源首地址
stream --> 指定的流
返回值:
成功: 返回 非负值
失败: 返回 EOF
(4)、一次操作 某种结构的数据
size_t fread(void *buf, size_t size, int nmemb, FILE *stream)
功能:
从指定的 stream 流中 读取 nmemb 个结构的数据到 buf 地址上,每个结构 size 个字节。
(也就是从 stream 流中 读取 nmemb * size 个字节的 数据到 buf 地址上)
参数:
buf --> 存放数据的缓存区首地址
size --> 读取的结构的大小
nmemb --> 读取的结构的个数
stream --> 指定的流
返回值:
成功: 返回实际读取到的 结构的个数
失败: EOF
size_t fwrite(void *buf, size_t size, int nmemb, FILE *stream)
功能:
将 buf 地址的前 nmemb * size 个字节的 数据,写入到 stream 流中
参数:
buf --> 存放数据的缓存区首地址
size --> 读取的结构的大小
nmemb --> 读取的结构的个数
stream --> 指定的流
返回值:
成功: 返回实际写入的结构的个数
失败: EOF
1.7.3、关闭
int fclose(FILE *stream);
备注:cat -> a.txt //终端可以直接输入数据
思考练习:
1、如何计算 缓存区的大小。 https://blog.csdn.net/weixin_43793181/article/details/104300767
2、如何计算 当前终端中可以打开的文件个数。
练习:
1、使用标准IO,实现不同路径的文件的拷贝(fgetc/fgets/fread)。复制
2、题目要求:编程读写一个文件test.txt,每隔1秒向文件中写入一行数据,
类似这样:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
该程序应该无限循环,直到按Ctrl-C中断程序。
再次启动程序写文件时可以追加到原文件之后,并且序号能够接续上次的序号,比如:
1, 2007-7-30 15:16:42
2, 2007-7-30 15:16:43
3, 2007-7-30 15:19:02
4, 2007-7-30 15:19:03
5, 2007-7-30 15:19:04
提示:
1、sleep(1);
2、要追加写入文件,同时要读取该文件的内容以决定下一个序号是几,
应该用什么模式打开文件?
首先判断一下打开的文件是否为新文件,
如果是新文件,就从序号1开始写入;
如果不是新文件,则统计原来有多少行,比如有n行,
然后从序号n+1开始写入。以后每写一行就把行号加1。
获取当前的系统时间需要调用函数time(),
得到的结果是一个time_t类型,其实就是一个大整数,
其值表示从UTC时间1970年1月1日00:00:00(称为UNIX的Epoch时间)
到当前时刻的秒钟数。
然后调用localtime()将time_t所表示的UTC时间转换为本地时间
(我们是+8区,比UTC多8个小时)并转成struct tm类型,
该类型的各数据成员分别表示年月日时分秒,
请自己写出转换格式的代码,不要使用ctime()或asctime()函数。
具体用法请查阅man page。time和localtime函数需要头文件time.h。
调用sleep(n)可使程序睡眠n秒,该函数需要头文件unistd.h。
二、文件IO
2.1、不带缓存的系统调用接口函数对文件进行IO操作。
2.2、文件描述符:
是当前系统最小的、未用的非负整数。
是内核用来标识操作的文件。
文件IO所有操作都将围绕 文件描述符进行。
2.3、API接口
2.3.1、打开(创建打开)
int open(const char *path, int oflag);
int open(const char *path, int oflag, mode_t mode);
功能:
以oflag方式打开或者创建 path 代表的文件。
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
参数:
path: 代操作的文件名(可包含路径)
oflag:文件操作方式:O_RDONLY(只读)、O_WRONLY(只写)、O_RDWR(可读写)
O_CREAT(文件不存在就创建)
mode: 文件存根权限,只有 O_CREAT 出现时才使用
返回值:
成功:返回文件描述符
失败:返回 -1,并设置错误信息
2.3.2、读写数据
int read(int fd, void *buf, size_t n);
功能:
从 fd 对应的文件中读取 n 个字节的数据到 buf 地址上。
size_t <==> ssize_t <==> int
头文件:
#include <unistd.h>
参数:
fd: 文件描述符,open函数成功的返回值
buf: 保存数据的缓冲区首地址,用来存储读取到的数据
n: 要读取的字节数
返回值:
成功:
> 0:表示读取的实际字节数。
= 0:表示读取到文件末尾
失败:
-1:设置错误信息
int write(int fd, void *buf, size_t n);
功能:
将buf 地址上的前 n 个字节数据写入到 fd 对应的文件中
头文件:
#include <unistd.h>
参数:
fd: 文件描述符,open函数成功的返回值
buf: 要写入的数据来源地址
n: 要写入的字节数
返回值:
成功:
> 0:表示写入的实际字节数。
失败:
-1:设置错误信息
2.3.3、关闭文件
int close(int fd);
2.3.4、文件的偏移定位
off_t lseek(int fd, off_t offset, int whence);
功能:
将 fd 对应的文件的指针以 whence为基准点,偏移 offset个字节,返回偏移后文件指针的位置。
2.3.5、获取文件属性:
int stat(const char *path, struct stat *buf);
功能:
获取path对应的文件的属性,到 buf 地址上。
头文件:
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
参数:
path: 待操作的文件名(可包含路径)
buf: 用来存储文件属性的首地址
返回值:
成功:0
失败:-1,并设置错误信息
2.3.6、打开目录
DIR *opendir(const char *path);
功能:
获得 path 目录中的所有文件信息,返回目录流指针
头文件:
#include <sys/types.h>
#include <dirent.h>
返回值:
成功: 目录流
失败: NULL,并设置错误信息
2.3.7、获取目录刘中文件信息
struct dirent * readdir(DIR *dir);
功能:
读取目录流中,一个文件的信息。
2.4、静态库和动态库的制作和使用:
静态库:
程序在编译阶段,加载库,代码体积变大,运行时与库函数无关,方便移植。
库文件名: lib*.a
动态库:
程序在运行时,加载库,代码体积不变,节约空间和资源。
库文件名: lib*.so
1、静态库制作步骤:
1、将 *.c 编译成 *.o
gcc -c *.c
2、将 目标文件 *.o 生成 lib*.a 静态库文件
ar crs lib*.a *.o
注意:
libadd.a :为静态库文件名
add :为库名
2、静态库文件的使用
gcc main.c -L. -ladd
注意:
-L :指定库的搜索路径
. :此处表示当前路径
-l :链接库名
练习:
1、使用文件IO,实现图片的拷贝。
*.jpg *.png
2、使用代码实现,"ls -l file.txt"。
三、进程
3.1、理论知识:
1、进程:程序的一次执行过程,是系统资源分配和调度的最小单位,是动态的。
2、程序:存储在磁盘上的指令的有序集合,是静态的。
3.2、进程的内容:
数据段:全局变量,malloc分配的空间
正文段:程序的代码行
堆栈段:函数返回值、参数、局部变量等
3.3、进程的类型:
3.3.1、交互进程:由 shell 控制和运行的进程,也就是在终端中运行产生的进程。
3.3.2、批处理进程:不属于某个终端,是在队列中被顺序执行,操作系统开机时就有很多批处理进程。
3.3.3、守护进程:与终端无关,开机时自动执行,系统关机时 结束。(系统时间、系统中的服务器)
3.4、进程的状态
就绪态:进程准备运行
运行态:进程正在运行
等待态:(休眠态)进程正在等待一件事情或者系统资源
可中断和不可中断
停止态:进程被中止的状态,还可以重新运行
死亡态:进程被终止(终结),但是task_struct还存在,也就是进程资源还没有回收。
3.5、进程相关的指令
3.5.1、以某一个优先级运行程序,产生进程
nice -n NUM 可执行程序
eg:
nice -n 5 ./a.out 以优先级为 5 运行程序
3.5.2、更改正在运行进程 的优先级
renice -n NUM PID
eg:
renice -n 2 14326 :将14326进程的优先级改为 2
注意:
用户所能设置的优先级为 0 ~ 20,其中0 为最高优先级,20 为最低
3.5.3、
ctrl+z 将前台运行的进程,在后台挂起,暂停(停止),同时后台进程会有编号
bg 编号: 将后台停止的进程,在后台运行
fg 编号: 将后台运行的进程,转到前台运行
./a.out & :后台运行 a.out可执行程序
3.6、系统调用接口
3.6.1、创建子进程
pid_t fork(void);
功能:
创建子进程。
头文件:
#include <sys/types.h>
#include <unistd.h>
参数: 无
返回值:
成功:
返回 0 : 表示子进程区域
返回 >0(子进程ID号): 表示父进程区域
失败:
-1,并设置错误信息
3.6.2、相关考点概念:
僵尸进程: 子进程先于父进程结束,父进程没有回收子进程资源。
孤儿进程: 父进程先于子进程结束,子进程被系统 init.d 托管。
3.6.3、在进程中执行另外一个可执行程序,产生新的进程。
原进程中除了进程号,其他的都将被替换。
(1)、
int execl(const char *path, const char *arg, ....);
以 l结尾,表示第一个参数必须是 可执行文件名包含路径,后面以列表形式填写参数
第二个参数,必须是可执行程序的 执行命令
第三个参数,可以是 执行命令的 参数选项
最后一个,必须以 NULL 结束
(2)、
int execlp(const char *file, const char *arg, ....);
以 lp结尾表示,
第一个参数,可执行文件名,后面以列表形式填写参数,自动搜索文件的路径
第二个参数,必须是可执行程序的 执行命令
第三个参数,可以是 执行命令 的 参数选项
最后一个,必须以 NULL 结束
3.6.4、结束进程的函数
1、库函数 #include <stdlib.h>
int exit(int status);
结束进程,并且刷新缓冲区
2、系统调用 #include <unistd.h>
int _exit(int status);
结束进程,不会刷新缓冲区
3.6.5、僵尸进程的解决方法
(1)、wait 、 waitpid
1、pid_t wait(int *status);
功能:
阻塞父进程,等待任何一个子进程结束,回收资源
头文件:
#include <sys/types.h>
#include <wait.h>
参数:
status: 保存子进程退出时的状态或者 exit函数的实参,
可以使用 固定的宏函数实现(WIFEXITED, WEXITSTATUS)
返回值:
成功: 返回退出的子进程PID
失败: 返回 -1
(2)、pid_t waitpid(pid_t pid, int *status, int option);
功能:
阻塞父进程,等待子进程结束,回收资源
头文件:
#include <sys/types.h>
#include <wait.h>
参数:
pid : -1, 等价于wait,回收任何一个退出的子进程资源
>0, 为子进程PID, 表示指定要回收的子进程
status: 保存子进程退出时的状态或者 exit函数的实参,
可以使用 固定的宏函数实现(WIFEXITED, WEXITSTATUS)
option: WNOHNG : 非阻塞模式
0 :阻塞模式,等价于 wait
返回值:
成功:
阻塞模式下: 返回退出的子进程PID
非阻塞模式下:返回 0
失败: 返回 -1
3.6.6、孤儿进程的应用
(1)、守护进程创建步骤
1、创建孤儿进程 (摆脱父进程的控制)
fork();
2、创建新的会话期 (拜托终端控制,将孤儿进程独立出来)
setsid();
3、更改工作目录
chdir(...);
4、重设权限掩码
umask(0)
5、关闭文件描述符
6、根据具体功能要求,实现算法
练习:
1、创建一个多进程,子进程实现文件行数的统计,父进程实现计算文件大小。
2、创建一个守护进程,实现:
每隔一秒,向文件中写入:行数+日期
思路:
1、先创建守护进程
2、在守护进程第五步操作之后,加入文件读写。
1、打开文件
2、计算文件中的行数
3、循环的获取系统时间、日期
4、在循环中将行数+1与日期拼接在一起
5、写入文件中
https://www.jb51.net/article/118822.htm
四、线程
4.1、多进程的特点:
4.1.1、缺点:
1、每个进程的地址空间相对独立,比较消耗系统的空间资源
2、进程间进行任务切换时,要不停的刷新cache缓存器和TLB页表,比较消耗系统的时间
4.1.2、优点:
1、能实现多任务
2、地址空间相对独立,每个进程不会形成干扰,
也就是子进程结束,不会影响父进程的运行;父进程结束,不会影响子进程的运行。
4.2、线程:
4.2.1、概念:轻量级的进程,共享同一地址空间的多个任务。
4.2.2、特点:
(1)、优点:
1、同一进程中的多个线程,共享该进程地址空间,节约系统空间资源。
2、同一进程中的多线程,进行任务切换时,提高切换的效率,
避免额外的刷新cache和TLB页表的系统时间。
3、同一进程中的多个多线程之间进行数据传递比较方便,可以使用全局变量。
(2)、缺点:
1、同一进程中的一个线程意外结束或死掉,那么该进程中的其他线程都不能继续运行。
2、同一进程中多个线程,容易竞争共享资源,也就是资源抢占问题。
3、线程所属的进程结束,那么线程就不存在。
4.3、库函数
4.3.1、创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void * (* routine)(void *), void *arg)
头文件:
#include <pthred.h>
参数:
thread :保存线程id 号的地址
attr :设置线程的属性,通常使用 NULL 缺省属性
routine :线程的执行函数名,执行函数为 void 类型的指针函数
arg :要传递到线程的数据,如果不传数据使用 NULL
返回值:
0 :成功
-1 :失败,并设置错误信息
程序编译时,必须 -lpthread
4.5、线程间的通信机制:
4.5.1、同步通信:
多个任务按照某种约定的顺序共同配合的完成一件事情。线程中使用 信号量来实现。
4.5.2、信号量:
系统中的资源数量。
4.5.3、api接口:
(1)、初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value)
头文件:
#include <semaphore.h>
参数:
sem : 存储信号量的地址
pshared : 信号量的使用范围(0:线程中使用,非0:进程中使用)
value : 信号量的初始值
返回值:
成功: 返回 0
失败: 返回-1
(2)、p操作,申请资源,相当于消费者
int sem_wait(sem_t *sem);
功能:
申请资源(也就是判断信号量的值)
如果信号量值为 0, 阻塞,当信号量值 不为 0,被唤醒继续运行
如果信号量值为 >0, 非阻塞
资源申请成功,信号量值 -1
参数:
sem : 信号量
返回值:
成功 :0
失败 :-1
(3)、v操作,释放资源,相当于生产者
int sem_post(sem_t *sem);
功能:
释放资源(也就是将信号量的值 + 1)
如果系统中有等待该资源的任务,就立马唤醒该任务继续运行
如果系统中没有等待该资源的任务,系统中信号量的数值 + 1
参数:
sem :信号量
返回值:
成功: 0
失败:-1
(4)、临界资源:
多个任务共享的全局数据。
(5)、临界区:
访问操作临界资源的代码行。
(6)、锁粒度:
互斥锁保护的临界区的大小。临界区越大,锁粒度就越大。
(7)、保护临界资源的方法:互斥锁
1、互斥锁目的:
保护临界资源,保证数据的完整性,一个线程使用临界资源中,另一个线程就不能使用。
2、api接口:
1、初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr) ;
头文件:
#include <pthread.h>
参数:
mutex : 保存互斥锁id 的地址
attr : 互斥锁的属性,一般写 NULL
返回值:
成功:返回 0
失败:返回-1
2、加锁(申请互斥锁)
int pthread_mutex_lock(pthread_mutex_t *mutex)
参数:
mutex :互斥锁
返回值:
成功:返回 0
失败:返回-1
3、解锁锁(释放互斥锁)
int pthread_mutex_unlock(pthread_mutex_t *mutex)
参数:
mutex :互斥锁
返回值:
成功:返回 0
失败:返回-1
3、互斥锁使用时,需要注意:
1、同一临界资源的所有临界区,必须使用同一把互斥锁。
2、每个临界区之前加锁,临界区最后必须解锁,不然会产生死锁。
4.6、动态库的制作和使用:
4.6.1、制作方式:
1、编译生成与地址无关的程序,*.o
gcc -fPIC -c *.c
2、将 *.o 编译生成动态库文件 lib*.so
gcc -shared *.o -o lib*.o
4.6.2、动态库的使用
1、如果动态库文件在程序的当前目录
gcc main.c *.c -l*
2、如果想将动态库制作成第三方库,在系统的任何路径都可以使用
1、将 lib*.so 拷贝到 /usr/lib 或者是 /lib 目录
2、将动态库文件对应的头文件 *.h,拷贝到 /usr/include目录
编译时,直接:
gcc main.c *.c -l*
4.6.3、动态库步骤的制作:
gcc - fPIC -c add.cabs
mv add.c ../
gcc -shared add.o -o libadd.so
rm add.off_t
gcc main.c -ladd
练习:
1、编写多线程程序,实现线程1拷贝图片前半部分;线程2拷贝图片后半部分。
五、进程间的通信
5.1、进程间通信方式:
5.1.1、传统进程间通信方式
无名管道、有名管道、信号
5.1.2、(System V5)IPC对象通信方式
共享内存、消息队列、信号灯集
5.1.3、BSD套接字通信
网络编程 socket 通信
5.2、无名管道:
5.2.1、定义
是一个在内核中存在的特殊文件(看不到文件名),使用文件IO进行数据交互。
5.2.2、特点:
1、是一种半双工通信(数据只能一个方向传递),有固定的的读端和写端。
2、必须在具有亲缘关系的进程之间使用
5.2.3、api接口
1、创建无名管道文件
int pipe(int fd[2]);
头文件:
#include <unistd.h>
参数:
fd :数组名,用来保存读端和写端的文件描述符
返回值:
成功: 返回 0
失败: 返回-1
5.2.4、注意:
1、管道中没有数据,读端会阻塞等待,直到有数据
2、读端不存在时,写端写入数据,会收到内核的SIGPIPE信号,终止进程的运行。
3、管道中缓冲区满了,写端写入数据将会阻塞,直到读端读取数据。缓冲区64k = 1024*64
5.3、有名管道:
5.3.1、定义:
是一种特殊的管道文件,在本地磁盘可见
5.3.2、特点:
1、可以在具有亲缘关系的进程间使用,也可以在非亲缘关系的进程中使用。
2、数据遵循先进先出
3、可以使用文件IO中除了lseek之外的函数操作
5.3.3、api接口
1、创建管道文件
int mkfifo(const char *filename, mode_t mode);
头文件:
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
参数:
filename: 要操作的管道文件名(可包含路径)
mode: 8进制的权限
返回值:
成功: 返回0
失败: 返回-1
5.3.4、注意:
1、有名管道文件的数据交互在 内核中,本地磁盘文件中没有数据
2、有名管道的读进程结束,写进程写入数据会终止程序运行
3、有名管道的写进程结束,读进程会一直运行,读到0直接数据
信号处理原型
void(*signal(int signum,void(*handler)(int)))(int);