文件IO和进程线程
一、IO
文件IO: 如何通过代码操作文件,创建 删除 修改
进程线程: 多任务–多个进程同时工作,完成同一个任务
原理: 文件放在硬盘—硬件, 有系统管理
IO分为两个:
1.标准IO 标准C库提供的 通用的, 只针对普通文件
2.文件IO linux特有的 特有的 指针linux所有文件
linux除了支持普通文件外,还支持很多特有的文件,因为linux把一切都看做了文件.
标准IO:
操作: 创建 删除 打开fopen 关闭fclose 读fread和写fwrite 其他操作
读: 将硬件文件 读取到 内存中. 修改
写: 将内存数据写入到物理文件中.
函数:
1.fopen : file open
FILE * fopen(文件路径,指定读写权限)
FILE *fp=fopen(argv[1],"r");
2.fclose
fclose(fp);
3.fread
int ret1 = fread(buf,1,sizeof(buf),fp);
4.fwrite
int ret2 = fwrite(buf,1, ret1 ,fp);
5. fgetc fputc fgets ,他们是基于 fwrite fread实现的
char fgetc(FILE *fp); 从文件中读取一个字符出来
int fputc(char ch,FILE *fp) 向文件中写入一个字符
fgets: 实现从文件中读取 一行数据.
char *fgets(char *buf,int bufsize, FILE *fp )
6、fopen之后,位置指针指向文件头部,fread /fwrite 会自动调整 位置指针.
ftell() ----获取文件 当前位置指针的 位置(相对于文件头部)
fseek() ---改变文件位置
7、errno --错误
FILE *fopen(char *path,char *mode);
可能出错,比如 文件不存在,比如 权限不满足 ......
当你fopen失败的时候,你可能想知道 错误的具体原因,编译器帮你添加的.
linux系统引入了一个 全局的整形变量errno
当系统函数失败的时候,系统函数会把失败原因存放在errno中.使用见代码
用法:
例如:读入错误判断
int ret = fread(buf,1,sizeof(buf),fp);
if(ret<0){
perror("fread err");
return -11;
}
文件IO:
删除文件 unlink, 独立于标准IO和文件IO
int unlink(char *path)
创建目录,删除目录,查看目录内容
1、查看目录内容: direction
打开目录 opendir
xx = opendir("目录路径")
2、读取目录内容 readdir
{1.c,2.c subdir} = readdir(xx)
3、关闭目录 closedir
closedir(xx)
目录,文件,目录项:
/home/lsf/dir/1.c
/ ,home,lsf,dir ---目录
1.c ------------文件
/,home,lsf,dir,1.c---目录项: 是目录和文件的统称
路径都是有目录项构成的
所用函数总结:
fopen fclose fread fwrite fseek ftell fgets stdin stdout stderr
open close read write lseek 0 1 2
二、进程与线程
1、进程
多任务: --------- 好处, 同时处理多个任务
-
不相关的程序 多任务
-
相关程序的 多任务进程 任务 程序
程序 a.out — 文件,静态
进程: 运行的程序 -动态: 1.程序在执行 2.动态访问资源(malloc open close )
当你在终端输入命令(程序)的时候,终端会把程序拷贝到内存中去.
然后执行内存的程序-----成为进程
任务: 进程就是为了完成某个任务
查看所有进程: 命令 : ps -aux
系统有上百个进程,如何进行区分 -------> 给每个进程分配一个id, 称为 pid: process id 进程号
linux系统中,所有的进程都有父子关系,这一切的源头是 一个pid=1进程,一般称为init进程
任何进程都是有他的父进程创建的,源头是 pid=1 init进程
父进程号: ppid parent pid 查看父进程------> ps -auxf
###如何产生子进程 ------> int fork (void)
main()
{
.....
fork();
....
while(1){
printf("run\n");
sleep(1);
}
}
为什么要生产 子进程:
-
完成新的任务
-
多任务原理,CPU快速乱转执行进程
原理:
进程不可能无端产生
像是克隆clone:
产生了两个一模一样的个人, 独立两份空间,改变一个没有改变另一个
进程终结:
main()
{
while(n--){
}
}
exit(0); 编译器会自动帮你添加一行.
exit:结束进程,并释放进程所占有的资源( 1.malloc 2.打开的文件 .... )
1.退出main函数 本质上还是调用了exit
2.exit() 进程主动调用exit函数结束自己,不需要退出main函数
#include <stdlib.h>
void exit(int status);
status这个值,传给父进程的,父进程会获取这个值,并进行响应的处理
exit之后,进程还有一点点残余留在内存中, 僵尸进程
如何彻底释放/收尸: 父进程来收尸
如果父进程先死,则有爷爷来收尸.......最终 归到 pid=1进程
status,我们只用了 低8bit.所以要进行处理--> stu= (status&0xFF00)>8
3._exit() 进程调用它也可以结束.
#include <unistd.h>
void _exit(int status);
他们都会结束当前进程,并释放进程的资源, 并传递参数给父进程
exit: 它 会 清理标准io中的 文件缓存.
_exit: 它 不会 清理标准io中的 文件缓存.
收尸:
pid_t wait(int *stat) 阻塞等待…
exec 家族:
我们创建子进程,大部分时候是为了执行新的程序. 而不是执行某个程序的分支.
execl execv execvp execlp…
进程间通信:
原理: 进程运行在os至上,os提供了很多共享资源,让他们进行通信
最简单: 共享文件 共享内存 信号 …
=第一种 管道FIFO===
特点: FIFO 单向
管道本质上就是 在os中创建一个 临时文件,一个进程写入,一个进程读取
管道对文件进行了一点点改动:
1.文件是有大小的,一般只有4K/8K.
2.写入则存放数据,读取则删除数据;
3.如果满了,继续写—写进程阻塞等待空间
如果空,继续读—读进程阻塞等待
===============第二种 共享内存 share memery ==============
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zc43ZQgq-1661734599390)(G:%5C0001_huaqing_23800%5C%E5%B5%8C%E5%85%A5%E5%BC%8F_%E7%AC%94%E8%AE%B0%5C4.%E6%96%87%E4%BB%B6io%E5%92%8C%E8%BF%9B%E7%A8%8B%E7%BA%BF%E7%A8%8B%5C%E7%AC%94%E8%AE%B0%E5%9B%BE%E7%89%87%5C27.%E5%85%B1%E4%BA%AB%E5%86%85%E5%AD%98%5C2.jpg)]
==第三种 信号 signal=
信号: 又称为 软件中断
进程执行循环任务,突然 系统给他一个信号,进程会暂停任务,转而去执行 信号处理函数,之后 返回继续执行任务.
优点:实时性很高
signal_handle()
{
}
main()
{
signal(2, signal_handle );
while(1){
打游戏
吃饭
睡觉
....
}
}
接收信号:
进程执行循环任务,突然 系统给他一个信号,
进程会暂停任务,转而去执行 信号处理函数,
之后 返回继续执行任务.
信号种类: 他们都是正数
系统信号:
SIGINT: signal interrupt 打断信号, ctrl+c
SIGKILL: signal kill 杀死进程信号, kill -9 pid
SIGSEGV: signal segment 段错误信号
用户信号:
SIGUSR1 SIGUSR2
进程对收到的每一种信号,都有默认的处理方式 每种信号处理方式不一样一般默认都是 exit( )
进程如何指定 某种信号的处理方法 ---> signal( 信号, 处理方法 )
发送信号:
1.kill 命令 : kill -信号值 pid
2.kill 函数 :
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
pid:接收信号的进程
sig:信号值
返回值: 0-success -1:errno
##### 定时器 #########
原理: 系统每隔一段时间,给进程发送一个信号SIGALRM, alarm--
1. 进程指定收到SIGALRM信号后的动作 signal(SIGALRM,xxx_fun )
2.如何让系统 一直 间隔 给你发信号 int alarm( int sec )
如果信号还没有到达时间,又来了一次alarm,会覆盖以前的请求
特殊: alarm(0) 立马删除所有的闹钟,当前不发生信号
##### 守护进程 ############
进程一直在干活.
如果一个进程在某个终端运行,当终端被关闭的时候,
原理: 终端在死去之前,会给每一个子进程发送一个信号 SIGHUP, handup-挂断
1.当子进程收到信号之后,默认exit. 你可以替换进程默认的处理方式
signal(SIGHUP, xxxxx);
2. nohup a.out ####### 使用nohup命令来执行程序,你的程序就不在遭受hup的伤害
一般nohup 要和& 一起使用, &:让进程去后台执行
效果 nohup ./xxxxx.out &
注意事项: 信号处理函数,不要处理耗时的任务 包括延时 sleep(1)。
2、线程
进程可以实现多任务 但是:
-
1.父进程拷贝自己生成子进程,浪费内存
-
2.进程间通信 比较复杂和麻烦
轻量级的多任务… 线程thread
原理: 一个进程内部可有多个执行单元 同时执行 , 每一个执行单元称为 线程 这些线程,都在进程内部,共享进程的资源, 即不需要(进程间)通信了.
一个进程内部可以有很多线程,如何区分这些线程 —> 每个线程都有一个id, tid:thread id
int aa,bb,cc,ee ,ff; 这些资源属于进程,大家都可以访问得到!!! 全局的变量是大家共享的
线程是共享(进程)资源的,但是每个线程都有自己独立空间
thread_fun( struct tmsg *xxt )
{
xxt线程得到的参数
int xx,yy,zz; 这些东西属于该线程独有的
这是另外一个执行单元
while(1){
sleep(1);
}
}
int main(int argc,char **argv)
{
int mm,nn,oo; 这些东西属于主线程独有的
struct tmsg xxt;
//创建另外一个执行单元
tid = pthread_create(, 线程的执行函数, &xtt-给线程传递的参数 );
//得到该线程的tid
//执行单元1----
while(1){
//主进程以后都在这里面干活
sleep(1);
}
}
线程的退出:
-
1.线程结束while(1)即可, return 参数
-
2.线程调用pthread_exit(参数),两者效果一模一样
主线程如何控制子线程的死亡呢???
建议用法,: 使用一个全局变量 来控制子线程
int global_run ;
子线程 while(global_run) {
}
main(){
global_run=0/1;
}
进程的优点:
-
进程内线程共享资源的,包括全局变量 信号 还包括文件
-
如果一个进程内部有很多线程,任何一个线程犯错 系统会给进程发段错误信号 整个进程结束…
-
如果每个任务都是一个进程,某个死了 大家没事,还可以重启该进程…
线程的同步:
并发问题: 并行发生, 同时发生的
生活中: 同时访问同一个资源,造成的问题. 一般是不同时访问资源…
如何解决呢: 加锁 线程互斥锁: mutex—互斥–互斥访问
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8zwVHTQ4-1661734599391)(C:%5CUsers%5CWYH%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20220829005951826.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HeMj6LMc-1661734599392)(C:%5CUsers%5CWYH%5CAppData%5CRoaming%5CTypora%5Ctypora-user-images%5Cimage-20220829010106303.png)]
用一个全局变量 来控制子线程
int global_run ;
子线程 while(global_run) {
}
main(){
global_run=0/1;
}
进程的优点:
-
进程内线程共享资源的,包括全局变量 信号 还包括文件
-
如果一个进程内部有很多线程,任何一个线程犯错 系统会给进程发段错误信号 整个进程结束…
-
如果每个任务都是一个进程,某个死了 大家没事,还可以重启该进程…
线程的同步:
并发问题: 并行发生, 同时发生的
生活中: 同时访问同一个资源,造成的问题. 一般是不同时访问资源…
如何解决呢: 加锁 线程互斥锁: mutex—互斥–互斥访问
[外链图片转存中…(img-8zwVHTQ4-1661734599391)]
[外链图片转存中…(img-HeMj6LMc-1661734599392)]