苏嵌实训——day14
一、IO
1.1 fseek
头文件: #include <stdio.h>
原型:int fseek(FILE *stream, long offset, int whence);
功能:读写指针的偏移
参数:
stream:目标文件流指针
offset:如何偏移,偏移多少
如果为负数,代表向前偏移,如果偏移出了文件的开头,会返回报错。
如果该数为正数,代表向后偏移,如果偏移除了文件的末尾,会扩大文件,用'\0'来填,那么此类文件称为空洞文件
注意:如果偏移后没有对其进行任何写入操作,内核认为该偏移无效,不会扩大文件大小
whence:基准位置 ------》根据哪一个位置进行偏移
SEEK_SET:根据文件开头进行偏移
SEEK_CUR:根据用户当前位置进行偏移
SEEK_END:根据文件末尾进行偏移
返回值:
成功返回0
失败返回-1
#include <stdio.h>
char ch = 0;
int main(int argc, char const *argv[])
{
FILE *fp = fopen("./1.txt","r+");
if(NULL == fp)
{
perror("fopen");
return -1;
}
//文件中的数据为helloworld
//从文件的末尾向后偏移----空洞文件
fseek(fp,2000,SEEK_END);
fputc('a',fp);
ch = fgetc(fp);
printf("ch = %c\n",ch);
fseek(fp,1,SEEK_CUR); //从当前位置向后偏移一个字节
ch = fgetc(fp);
printf("ch = %c\n",ch);
fseek(fp,-1,SEEK_END);
ch = fgetc(fp);
printf("ch = %c\n",ch);
return 0;
}
1.2 sprintf
头文件: #include <stdio.h>
原型:int sprintf(char *str, const char *format, ...);
功能:向一个固定的地址存放字符串(一般用于字符串的拼接)
参数:
str:要存放格式化完成的字符串的地址
format:格式化字符串
...:可变参数(一般放置变量)
返回值:
成功返回输出的字节个数
失败返回负数
#include <stdio.h>
int main(int argc, char const *argv[])
{
char name[20] = "zhangsan";
int age = 18;
char sex = 'w';
char phone[12] = "12345678900";
char buf[123] = {0};
sprintf(buf,"name:%s--age:%d--sex:%c--phone:%s",name,age,sex,phone);
printf("buf = %s\n",buf);
return 0;
}
1.3 snprintf
头文件: #include <stdio.h>
原型:int snprintf(char *str, size_t size, const char *format, ...);
功能:按照固定的大小去格式化字符串输出到字符地址中
参数:
str:要存放格式化完成的字符串的地址
size:大小(规定要写入str这片地址中的字节大小)
format:格式化字符串
...:可变参数(一般放置变量)
返回值:
成功返回输出的字节个数
失败返回负数
1.4 fprintf
头文件: #include <stdio.h>
原型:int fprintf(FILE *stream, const char *format, ...);
功能:格式化输出字符串到文件中(一般用于书写日志文件)
参数:
stream:目标文件流指针
format:格式化字符串{固定的字符串和占位符}
...:可变参数(一般放置变量)
返回值:
成功返回输出的字节个数
失败返回负数
#include <stdio.h>
int main(int argc, char const *argv[])
{
char name[20] = "zhangsan";
int age = 18;
char sex = 'w';
char phone[12] = "12345678900";
FILE *fp = fopen("./1.txt","w");
if(NULL == fp)
{
perror("fopen");
return -1;
}
fprintf(fp,"name:%s--age:%d--sex:%c--phone:%s",name,age,sex,phone);
return 0;
}
二、缓冲区
预定义流
在程序开始之前,创建三个文件描述符,分别为0,1,2对应的标准输入,标准输出,标准错误输出,同时也在其基础上封装了三个预定义流指针
标准输入:stdin --->键盘文件
标准输出:stdout --->终端文件
标准错误输出:stderr --->终端文件
#include <stdio.h>
int main(int argc, char const *argv[])
{
char buf[123] ={0};
// fgets(buf,123,stdin); //从标准输入流中读取数据
//printf("buf = %s\n",buf);
//标准输出
// fprintf(stdout,"helloworld%s","123123");
//标准错误输出
fprintf(stderr,"helloworld%s","123123");
while(1);
return 0;
}
2.1 缓冲区
缓冲区是什么?标准IO在文件IO的基础上封装的一片存放数据的地址(一般用来存放不着急得数据),等到缓冲区这个地址得数据存满,或者说程序员手动刷新缓冲区,空间中得数据会被调用文件IO操作。
全缓冲:一般对文件得操作,缓冲区大小为4096个字节。(页)
刷新缓冲区得条件:
1.缓冲区满刷新缓冲区
2.程序结束刷新缓冲区
3.程序员手动刷新缓冲区
fflush
头文件:#include <stdio.h>
原型:int fflush(FILE *stream);
功能:刷新缓冲区
参数:目标文件流指针
返回值:
成功返回:0
失败返回:EOF
#include <stdio.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
FILE *fp = fopen("1.txt","w");
if(NULL == fp)
{
perror("fopen");
return -1;
}
#if 0
while(1)
{
fprintf(fp,"helloworld");
usleep(10000);
}
#endif
fprintf(fp,"helloworld");
fflush(fp);
while(1);
return 0;
}
2.2 行缓冲:
只有两个行缓冲,标准输入,标准输出,行缓冲得大小为1024个字节
刷新行缓冲得条件:
1.缓冲区遇到\n则刷新缓冲区
2.缓冲区满则刷新缓冲区
3.当标准输入和标准输出一方要使用缓冲区时,正在使用得一方需要让出缓冲区,给另一方使用
4.fflush刷新缓冲区
5.程序结束
#include <stdio.h>
int main(int argc, char const *argv[])
{
int i = 0 ;
//验证缓冲区大小
for(i = 0; i < 1025;i++)
{
printf("1"); //每次向准备输出输入一个字节
}
//验证\n刷新缓冲区
printf("hello world\n");
char buf[123] = {0};
printf("helloworld1");
scanf("%s",buf);
while(1);
return 0;
}
2.3 无缓冲
一般为标准错误输出,一般用于比较着急得数据,所以不会进入缓冲区,直接调用文件IO
一般使用方式:fprintf(stderr,“hello world”);
后续报错信息:使用这种方式
文件IO和标准IO得区别
文件IO属于系统调用,由操作系统提供,速度快,但是频繁调用文件IO会降低内核得工作效率,并且移植性较差。
标准IO是由标准c库所提供,是在文件IO得基础上封装出来得API接口,移植性高并且在文件IO得基础上封装了一片缓冲区,降低了文件IO得调用次数,提高了内核得效率。
所以说我们得根据情况来使用者这两种IO模型。
三、进程
任务:目标结果
程序:是为了完成任务,编写得一段代码,是一个静态得
3.1 进程的概念
进程是程序为了完成任务执行得一次过程,是一个动态的,进程被称为资源分配的最小单位,因为每一个进程在启动初期,都会申请一个0-4G的虚拟空间。
这个空间分为两个部分,0-3G用户空间,3-4G是内核空间,0-3G是进程之间独有的空间,互不影响。3-4G属于多进程共享的空间(后续于进程间通讯使用),因为进程用户空间相互独立,互不影响,所以安全性较高。还会申请一个PCB进程控制块,是一个结构体,tast_struct里面存储了所有的进程资源
如:PC程序计数器,堆栈,文件描述符,进程的状态,进程号等。
操作系统启动时会自动出创建三个进程:
0:负责引导系统启动,也会创建一个1号进程 --》init进程
1:负责初始化硬件,回收资源
2:负责资源的分配,系统的调度
进程之间存在这个一种竞态。执行速度是不一定的,所以父子进程结束的快慢也是不一定的。
3.2 进程的调度机制:
3.3 进程的状态
3.4 进程的标志
进程号(PID):linux分配的进程的编号,每个进程都不一样,方便管理
进程在结束的时候,会释放进程号的所有权,其它进程等待它释放一段时间后分配,并不会结束后立马区分配。
3.5 进程相关的命令:
1.pstree
以树的形式显示所有的进程
如果加上-p参数会显示进程号
2.ps -ef
主要查看父子进程关系
PID 进程ID PPID 父进程ID
3. ps aux
主要查看进程的状态
进程的状态:
1.R:运行态
2.S: 休眠态
3.I:空闲态
4.T:停止态
5.Z:僵尸态
1. <:优先级高进程
2. N:优先级低
3. l:该进程中包含线程
4. +:前台进程
5. s:会话首进程
4. ps -ajx
主要用于查看家族关系
PGID:进程组ID
SID:会话ID
5. top -htop
动态查看进程信息:主要查看进程CPU占用率
6. jobs
查看用户后台进程列表
ctrl + z :会将前台运行的进程暂停保存到后台
fg:会将后台暂停的进程恢复到前台运行
fg + %序列号:将指定的后台暂停程序恢复到前台运行
bg:会将后台暂停程序在后台运行
bg + %序列号:制动哪一个进程
可执行程序名 + &:将进程运行在后台
3.6 创建进程:fork
头文件:#include <sys/types.h>
#include <unistd.h>
原型:pid_t fork(void);
功能:创建一个子进程
参数:无
返回值:
成功:返回给父进程子进程的ID号,返回给子进程 0
失败返回-1;
fork:失败的条件只有一个,内存不足。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char const *argv[])
{
int fd = open("./1.txt",O_RDWR|O_CREAT,0666);
if(-1 == fd)
{
perror("open");
return -1;
}
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
if(pid == 0)
{
printf("我是子进程\n");
write(fd,"hello world",11);
}
else if(pid > 0)
{
printf("我是父进程,我的子进程ID号为%d\n",pid);
char buf[123] = {0};
sleep(1);
lseek(fd,0,SEEK_SET);
//close(fd);
//fd = open("./1.txt",O_RDONLY);
read(fd,buf,sizeof(buf));
printf("buf = %s\n",buf);
}
return 0;
}
3.7 写时拷贝
fork函数创建子进程时要复制父进程的资源,但是子进程并不一定会用到这些资源,
所以说:
采用一种方式:当创建完子进程后,子进程先共享父进程的资源,如果双方有一方去修改内容,修改之前先复制一份到子进程中,这就叫做写时拷贝技术。
3.8 文件共享
如果fork之间打开了一些文件,获取了一些文件描述符,那么fork之后父进程和子进程公用这些fork之前获取的文件描述符。那么在操作过程中,有可能造成读写指针相互影响。
12345 6789
3.9 获取进程ID接口
3.9.1 getpid
头文件:#include <sys/types.h>
#include <unistd.h>
原型:pid_t getpid(void);
功能:获取自己的进程ID
参数:无
返回值:
成功:返回自己的进程ID
无失败
3.9.2 getppid
头文件:#include <sys/types.h>
#include <unistd.h>
原型:pid_t getppid(void);
功能:获取父进程的进程ID
参数:无
返回值:
成功:返回父进程ID
无失败
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
if(0 == pid)
{
printf("我是子进程\n");
printf("我是子进程,我的进程号为:%d\n",getpid());
printf("我是子进程,我的父进程号为:%d\n",getppid());
}
else if(pid > 0)
{
sleep(1);
printf("我是父进程\n");
printf("我是父进程,我的子进程号为:%d\n",pid);
printf("我是父进程,我的进程号为:%d\n",getpid());
printf("我是父进程,我的父进程号为:%d\n",getppid());
}
return 0;
}
3.10 三个结束进程的函数
exit
头文件:#include <stdlib.h>
原型:void exit(int status);
功能:结束一个进程,先释放缓冲区
参数:status:结束进程时的状态,同return 正常结束用0,非正常结束用-1
返回值:
无
_exit
头文件:#include <unistd.h>
原型:void _exit(int status);
功能:结束一个进程,不会释放缓冲区,直接结束
参数:status:结束进程时的状态,同return 正常结束用0,非正常结束用-1
返回值:
无
atexit
头文件:#include <stdlib.h>
原型:int atexit(void (*function)(void));
功能:注册一个进程结束后的运行函数
(当进程结束时会调用function这个函数)
参数:指向返回值为void 类型参数为void的函数指针
返回值:
成功返回0
失败返回非0值
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void fun(void)
{
printf("hello world!\n");
}
int main(int argc, char const *argv[])
{
//开始注册函数
atexit(fun);
sleep(3);
return 0;
}
3.11 孤儿进程:
父进程优先于子进程结束,子进程失去父亲后,子进程会认1号进程是自己的父进程。那么1号进程负责回收和管理子进程。如果说很多子进程都人1号进程为他的父进程,1号进程的负担会很大,所以我们在编写代码的时候,尽量让父进程回收完子进程资源之后再结束。孤儿进程是没有危害的。
3.12 僵尸进程
子进程优先于父进程结束,子进程会认为该父进程会回收自己的资源,但是父进程没有回收子进程资源的功能,或者说父进程一直在忙于自己的事情,未曾去回收子进程资源,子进程资源就得不到回收,但是子进程任务已经结束了,所以说子进程就变成了僵尸进程,僵尸是有危害。注意:以后在写
fork时都需要回收子进程资源。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
if(pid == 0)
{
exit(0); //子进程结束,父进程未结束,子进程就变成了僵尸进程
}
else if(pid > 0)
{
while(1)
{
sleep(1);
}
}
return 0;
}
3.12.1 处理僵尸进程
wait
头文件:#include <sys/wait.h>
原型:pid_t wait(int *stat_loc);
功能:阻塞等待回收任意一个子进程资源
参数:stat_loc:进程结束的状态(一般不考虑进程结束的状态直接写NULL)
返回值:
成功会返回接受到的进程的ID号
失败返回-1
注意:如果父进程调用wait来回收资源,那么会阻塞等待,会降低父进程的工作效率
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
if(pid == 0)
{
exit(0); //子进程结束,父进程未结束,子进程就变成了僵尸进程
}
else if(pid > 0)
{
printf("父进程运行中...\n");
sleep(5);
printf("接受成功,接受到子进程的ID号为%d\n",wait(NULL));
sleep(5);
}
return 0;
}
waitpid
头文件:#include <sys/types.h>
#include <sys/wait.h>
原型:pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:回收子进程资源,可以不阻塞回收也可以指定回收哪一个
参数:
pid:
pid == -1:回收任意一个子进程,如果采用阻塞方式与wait一样
pid == 0: 回收同进程组中的任意一个子进程
pid < -1;回收同进程组中,等于pid绝对值的子进程
注意: -:代表同组,对pid进行取绝对值,|pid| == 正数
wstatus:回收到的子进程结束时返回的状态
options:操作方式
0:阻塞回收
WNOHANG:非阻塞方式回收(如果去回收,没有紫禁城结束,立马返回,如果说已经有子进程结束,
立马回收)
返回值:
成功会返回接受到的进程的ID号
失败返回-1
注意:需要频繁的去调用函数去查看子进程结束与否
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
if(pid == 0)
{
sleep(5);
exit(0); //子进程结束,父进程未结束,子进程就变成了僵尸进程
}
else if(pid > 0)
{
pid_t pid1;
while(1)
{
printf("父进程运行中...\n");
if(0 < (pid1 = waitpid(-1,NULL,WNOHANG)))
{
printf("会受到子进程资源,子进程的ID号为: %d\n",pid1);
}
sleep(1);
}
}
return 0;
}
3.13 守护进程
是一种特殊的进程机制,默默地对我的工作进行服务的进程。是一个后台进程。init进程就是一个守护进程。后台进程:只允许向终端写入数据,不允许从终端读取数据。如果一旦对终端输入进行获取,那么就会立即终止后台进程。要脱离终端的管理,脱离回话的管理。守护进程一般用于:服务器,http,tftp,ftp。
附加:ctrl + c给前台所有的进程发一个终止信号
创建守护进程是有固定步骤的:
- 变成孤儿进程 kill:给进程发一个信号
- 创建一个新的会话,变成会首进程,setsid
头文件:#include <sys/types.h>
#include <unistd.h>
原型: pid_t setsid(void);
功能:创建一个新的会话,并且创建者称为会话的首进程
参数:无
返回值:
成功会返回一个新的会话ID号
失败返回-1
- 修改默认工作目录文件 chdir
头文件:#include <unistd.h>
原型: int chdir(const char *path);
功能:修改工作路径
参数:路径
返回值:
成功会返回0
失败返回-1 - 给与最高文件权限 umask
- 关闭文件描述符 close(0);注意:如果说也不会对终端进行操作,请关闭所有的文件描述符
- 开始做守护事件,如日志文件的写入。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
if(pid == 0)
{
//子进程
//创建一个会话,自己成为自己的主人
if(-1 == setsid())
{
perror("setsid");
return -1;
}
//修改工作目录
if(-1 == chdir("/"))
{
perror("chdir");
return -1;
}
umask(0);
close(0);
close(1);
close(2);
FILE * fp = fopen("./1.txt","w+");
while(1)
{
printf("11112\n");
//写日志文件
fprintf(fp,"helloworld\n");
fflush(fp);
sleep(1);
}
}
//不去管父进程,父进程会自动结束
return 0;
}
练习:使用两个进程共同去完成复制一个文件,将一个文件拷贝到另一个文件中
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char const *argv[])
{
//使用文件IO打开文件,获取文件大小
int fd = open(argv[1],O_RDONLY);
if(-1 == fd)
{
perror("open");
return -1;
}
int fd1 = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666);
if(-1 == fd1)
{
perror("open1");
return -1;
}
//获取文件大小,利用偏移量
int len = lseek(fd,0,SEEK_END);
printf("len = %d\n",len);
//需要一个从开头,一个从中间开始获取
len = len / 2;
pid_t pid = fork();
if(-1 == pid)
{
perror("fork");
return -1;
}
if(pid == 0)
{
lseek(fd,len,SEEK_SET);
lseek(fd1,len,SEEK_SET);
char buf[123] = {0}; //接收读到的输入,写入数据时使用
ssize_t ret =0;
while((ret = read(fd,buf,123)))
{
if(-1 == write(fd1,buf,ret))
{
perror("write");
return -1;
}
}
close(fd);
close(fd1);
}
else if(pid > 0)
{
//父进程 ----》从头开始
lseek(fd,0,SEEK_SET);
lseek(fd1,0,SEEK_SET);
char buf[123] = {0};
ssize_t ret = 0;
while(len)
{
if(len >= 123)
{
ret = read(fd,buf,123);
}
else
{
ret = read(fd,buf,len);
}
write(fd1,buf,ret);
len = len - ret;
}
}
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理