1.文件操作
因为在Linux系统中一切都可以看作是文件。并且 文件的管理格式 为 EXT4.
在LInux 系统中,只能 由 LInux 内核 操作 文件。所以用户不能直接 操作 内核。可以通过 系统调用访问 LInux内核,再由 LInux内核 去访问 文件。
系统调用 又可以 分为 c语言库函数调用 和 系统调用库函数调用
其中 系统调用库函数 可以直接访问 LInux 内核,而 c语言库函数 可以先 访问 系统调用,在通过系统调用库函数 访问 Linux内核,也可以直接访问 Linux内核。
总结:(应用程序 系统调用 标准款函数 LInux内核 之间的关系)
应用程序 如果 要访问 Linux内核(Linux内核 能 操作文件(注意:在Linux系统中 ,一切皆是文件)),需要 通过 系统调用 或 标准库 间接访问 Linux内核,标准库 有时 也可以调用 系统调用
访问 Linux内核。
补充:typedef 和 define
1.类型替换 ---typedef
格式:typedef 数据类型(要替换) 新名字;
例:
#include<sthio.h>
typedef int s;
int main()
{
int a=10;
s b=10;
if(a==b)
{
printf("a == b");
}
return 0;
}
2.宏定义:define
#define 新名字 被替换的内容
注意:这里被替换的内容,可以是常量,也可以是函数,也可以是表示。
例:
#include<stdio.h>
#define a 10//1第一种,替换常量
#define c 10> 20?10:20//第二种替换表达式
#define NX(A,B) A * B
int main()
{
int b=a;//等价于 b=10;
printf("max=%d\n",c);
//如果不用括号改变运算顺序的话,结果与实际会出现偏差
//如:NX(10+20,20+30)
printf("%d",NX(10+20,20+30));//运算顺序:10+20*20+30 =440
//所以要用括号去改变运行顺序
//正确的如下:顺序为 (10+20) *(20+30)
printf("%d",NX((10+20),(20+30)));
}
2.标准IO
c语言库函数中有关 文件操作的库函数 又 称为 标准IO,主要是对文件进行操作。
1.打开文件
FILE *fopen(const char *pathname,const char *mode) |
返回值:如果成功,返回一个指针,表示成功打开文件的地址
参数2:const char *mode
r:读打开。通常从文件开始的第一个位置开始读取
r+:读写打开。读和写的位置是同一个位置。默认从文件开始位置开始读写。
w:写打开,文件不存在,就创建。文件存在,就清空文件里的内容。
w+:读写打开 ,文件不存在,就创建。文件存在,就清空文件里的内容。
a:追加打开,在文件末尾开始追加新的内容。。如果不存在则创建文件,存在则在文件末尾进行操作(定位到文件末尾)
a+:以追加 和 读的方式打开。如果不存在则创建文件,存在则在文件末尾进行追加,读则在文件开始位置(但是读的位置,可能会因为不同系统,读的位置也会不同)。
特别:
b:以二进制形式操作文件。通常和 mode参数进行组合使用
2.关闭文件
int fclose(FILE *stream) |
通过打开文件的文件地址,来关闭打开的文件
注意:在程序执行过程中,系统会默认打开三个文件
1.stdin
2.stdout
3.stderr
3.读写文件
第一组:每次只进行一个字符的读写
读操作:fgetc
int fgetc(FILE *stream) |
成功:返回读取到的字符。
写操作:fputc
int putc(in c,FILE * stream)
int c:表示 要读入的 字符
FILE * stream:打开的文件
第二组 :一次操作一行字符串
读操作:fgets
char *fgets(char *s, int size ,FILE *stream); |
参数1:表示将 读取内容存放到该字符串地址中去
参数2:每次读取的长度
参数3:要读取的文件
注意点:
1.如果第一行中存在换行符("\n"),并且第一行的内容小于要读取的大小,那么在输出时 换行符也会被读取
2.使用fgets 会在末尾 添加 '\0'
写操作:fputs
int fputs(const char *s ,FILE *stream) |
参数1:要读入的字符串
第三组:读写若干个元素
读操作:fread
size_fread(void *ptr,size_t size ,size_t nmemb,FILE * stream) |
参数1:将读取的内容存放到该地址里去
参数2:读取的块大小
参数3:读取的块数量
返回值:成功:返回块的数量
注意:fread 函数 不会添加 ‘\0’
写操作:fwrite
size_fwrite(void *ptr,size_t size ,size_t nmemb,FILE * stream
参数1:将哪个地址中存放的数据读取出来,然后进行写入
参数2:写入块的大小
参数3:读取块的数量
4.补充
1.判断文件是否到末尾
int feof(FILE *stream) |
返回值:如果 已经 到达文件末尾 返回 1(真),
否则 返回 0(假)
2.判读文件是否出错
int ferror(FILE *stream) |
返回值: 如果 文件错误 返回 1(真),
否则 返回 0(假)
3.缓冲区
使用标准IO读写操作,在打开文件时会创建缓冲区,用于读写文件
注意:只有使用标准IO时才会有缓冲区机制
三种缓冲机制:
无缓冲---stderr
行缓冲:只有在缓冲区遇到'\n' 或者 缓冲区满,把缓冲区数据刷新到文件----stdin,stdout
全缓冲“只有缓冲区满,把缓冲区数据刷新到文件中----其他标准IO打开的文件
3.系统IO
系统IO是指计算机系统进行输入和输出操作的过程。
1.打开文件
int open(const char *pathname,int flags); (这个函数只能打开,如果不存在则打开失败) |
参数2:flags:文件的打开方式
三种基本方式:(下面三种最多只能同时存在一个,并且必须有下面中的任意一个)
1. O_RDONLY:只读方式打开
2.O_WRONLY:只写方式打开
3.O_RDWR :读写方式打开
其他方式:
O_APPEND:以追加方式打开(与只读打开排斥),必须有写
O_CREAT:如果文件不存在,则会创建文件(限制只能适用三个参数的函数 )mode参数 不使用,可以没有第三个参数
O_EXCL:与O_CREAT 一起使用,如果文件存在,则返回错误信息,只用于创建新文件
O_NOCTTY:如果文件是终端文件,终端就不能作为当前打开文件的程序的控制终端
O_TRUNC:如果文件存在,则清空文件
返回值:成功:打开文件的文件描述符。文件描述符作用就是标识文件
三种基本打开方式 和 其他 方式 进行组合。每个 方式之间 用 | 隔开。
注意:
1.如果使用 O_CREAT,必须在后面添加参数3,参数3 表示 文件的权限
文件权限的计算:参数3 & (-umask)
umask的取值:0002---八进制数
2. open 函数返回值 在 LINUX内核中,以下标为 3 开始,因为 在程序开始执行,系统会默认打开三个文件 stdin,stdout,stderr三个文件。
3. O_APPEND:在每次执行写的时候,都会把基准点便宜点文件末尾
2.关闭文件
int close(int fd) |
参数:int fd ;fd表示的就是文件描述符。
3.读文件
ssize_t read (int fd,void *buf,size_t count )
参数1:文件描述符,表示从哪个文件读取内容
参数2:将读取到的内存进行存储
参数3:每次读取的长度
返回值:成功:实际读取的长度
4.写操作
ssize_t write(int fd,const void *buf,size_t count)
参数2:从哪个文件写入
参数3:每次写入的长度
返回值:成功返回 写入的长度大小
5.文件读/写位置 偏移
在读入文件时,很多时候都是从文件开始的位置进行 读/写 ,下一次位置为上一次读/写结束的位置。也可以人为的进行修改。开始位置为0(下标)。
off_t lseek(int fd,off_t offset ,int whence)
参数2:偏移量。基于参数3 把 读/写位置 向前或向后 偏移 的长度。
负数:表示 向前 偏移 正数:表示向后偏移
参数3: 基准点。有三个固定的位置。
SEEK_SET 基准点为文件开始位置
SEEK_CUR 基准点为当前位置
SEEK_END 基准点为文件结束位置
例:
int fd=open("1.txt",O_WRONLY);
lseek(fd,45,SEEK_SET);//表示从文件开始位置,往后偏移45个字节。
int fd1=open("2.txt",O_RDONLY);
lseek(fd1,-10,SEEK_END);//表示从文件末尾位置,往前偏移10个字节,作为下次读写的开始位置。
int fd2=open("2.txt",O_RDONLY);
lseek(fd2,0,SEEK_END);//这个可以用来求打开文件的大小
补充:创建空洞文件
int fd=open("1.txt",O_RDONLY);
int size=lseek(fd,0,SEEK_END);//获取1.txt的文件大小
int fd2=open("2.txt",O_WRONLY |O_TRUNC |O_CREAT);//表示以写的方式打开,并清空里面的内容,如果文件不存在,就创建
lseek(fd2,sizee-1,SEEK_SET);
write(fd2,"\0",1);//文件2.txt里面的内容全是"\0"
6.文件属性
int stat(const char *pathname ,struct stat *statbuf) |
参数1:查看的文件名
参数2:结构体指针,存放文件属性
例:
struct stat s;//创建结构体,用查看文件属性
stat("1.txt",&s);//表示打开文件1.txt,查看1.txt的属性
printf("inode si %d\n",s.st_ino);//查看文件的inode号
//特殊:mode_t st_mode;//文件类型,文件权限
// 文件类型 12 ~ 15 位
// 文件权限:0~8位
printf("%x\n",s.st_mode & 01700000);//查看文件类型
printf("%x\n",x.st_mode & 0777);//查看文件权限
4.多文件 与 库的制作
1.gcc 编译流程---了解
1.预处理 ---只处理以 # 开头的语句,不进行语法检查
gcc xx.c -E -o xx.i
后缀名:.i,就表示 预处理文件
2.编译---将预处理文件转为汇编代码,会进程语法处理
gcc xxx.i -S -o xxx.s
后缀名:.s ,就表示 编译文件
3.汇编----将生成的 编译文件(汇编语言) ,转化为 二进制文件
gcc xxx.s -c -o xx.o
后缀名:.o ,就表示 汇编文件
4.链接-----将生成的多个汇编文件(.o文件)或 库文件 合并链接,最终形成一个可执行文件。
gcc xx1.o xx2.o xx3.o……
2.多文件
将一个完整且独立的函数功能,改写成一个头文件。不同的内容,可以写在不同的头文件中。
1.编写头文件--独立出函数功能
#define N 10
int and(int a,int b,int c)
{
return a+b+c+N;
}
int sub(int a,int b)
{
return a-b;
}
//当前文件名:math.c
2.头文件申明
#ifndef MATH_H
#define MATH_H
int and(int a,int b,int c);//申明函数
int sub(int a,int b);
#endif
//当前文件名:math.h
3.加载头文件
#include<stdio.h>
#include"math.h"
int main()
{
printf("sub=%d\n",sub(10,5));//调用函数功能
}
编译:
gcc 文件1.c 文件2.c 文件3.c 文件4.c……
补充:
1 < > 和 “ ”的区别.
< >:表示从库里面去找文件
“ ”:表示不仅要从库里面去找,还要从当前文件下去找
2.ifndef 作用
- 防止头文件的重复包含和编译;
- 便于程序的调试和移植;
3.库的制作
1.静态库
第1步.把程序文件编译为二进制文件,只编译,不链接
gcc -c xxx.c -o xxx.o
第2步:生成静态库(后缀名:.a)
ar -cr libxxx.a xxx.o xxx.o
//libxxx.a 就是静态库的名字
第3步:在编译万能我们自己的程序时,添加库
gcc xx.c xx2.c…… -L库的路径 -l库名 -I头文件路径
补充:
-L:库的路径
-l(小写L):链接库名,库的名字
-I (大写i):头文件路径(库的头文件)
2.动态库
第1步:gcc -fPIC -shard xxx.c -o libxxx.so (后缀名:.so)
-fPIC:忽略文件位置
-shard :共享库
第2步:gcc xx.c xx2.c…… -L库的路径 -l库名 -I头文件路径
注意:使用动态库的2种方法:
1.将自己的库,拷贝到系统库lib中
cp xx.so xx1.so……/lib
2.export LD_LIBRARY_PATH = 库的绝对路径
注意:该方法只能临时生效
静态库 和 动态库的 区别:
静态库:编译时,直接把库的内容编译到程序中
共享库:在编译时,只是把符号表添加到程序中
对程序的影响:静态库 比 共享库更大。其次,修改共享库的内容,运行共享库的程序文件中的内容也会发送改变。如果 修改 静态库,运行静态库的程序文件内容不会发生改变。
5.进程与线程
1.进程的概念
进程:简单点说就是程序的动态执行。程序的每一次执行都可以被称为进程
程序:只是一系列动作的指令操作文件
即:进程是动态的,程序是静态的
补充:进程和程序的区别:
进程:有完整执行的过程,包含程序运行起来的申请的空间,进程有PID
程序:没有完整执行的过程,程序只是一段代码,没有空间,没有PID标识
补充:查看进程的三种方法:
1.查看当前进程:ps + 进程格式(主要包括 :axu,bsd)
2.动态显示当前进程的情况:top
3.以Ubuntu系统为例,可以查看根目录下的/proc
2.进程的状态
最基础的三种状态:就绪态,运行态,等待(阻塞)态
就绪态:进程在就绪队列中排队,等待CPU调度
运行态:进程在CPU中执行
等待态:进程在等待某个事件调度,进入就绪态
3.进程的命令操作
1.进程的创建
pid_t fork(void);
返回值:父进程返回 子进程的 pid;
子进程返回 0;
父进程:哪个函数调用了fork函数,那么这个函数就被称为父进程
注意:子进程会从fork后的下一条语句开始执行。并且子进程拥有与父进程相同的内容,即大小相同,但是存储空间确实独立的。互不影响。
2.进程的关闭
三种方法:
1.主函数使用return 语句,直接结束进程。
注意:如果是在子函数中使用return语句,结束的是子函数,并不能结束进程
2.kill命令
kill + pid(要关闭的进程)
3.exit函数
void exit(int status)
参数:int status
返回值 & 0XFF 作为进程结束状态
void _exit(int status)
参数:int status
返回值 & 0XFF 作为进程结束状态
总结:两个函数的作用是一样的,都是退出进程
区别:exit函数会刷新缓冲区,_exit函数不会刷新缓冲区。
3.进程的等待
pid_t wait(int *wstatus) |
参数:用于接收子进程状态的地址
返回值:返回等待到的子进程的pid
注意:wait只会等待任意一个子进程结束,即有两个wait函数,会等待两个子进程结束
补充:
pid_t waitpid(pid_t pid ,int *wstatus,int options)
与wait函数作用一致。参数1表示哪个进程,参数2与wait函数的参数一样。只是参数3有变化
参数3:0:表示阻塞等待
WNOHANG:表示非阻塞等待
wait默认就是阻塞等待
进程
4创建守护进程
什么是守护进程:系统启动时,就自动启动,系统关闭,自动关闭
1.创建子进程,退出父进程
int pid=fork();//创建子进程
if(pid > 0)//调用fork函数创建子进程,会有两个返回值,父进程返回子进程的pid,子进程的pid为0
{
exit(0);//退出父进程
}
2.在子进程中创建会话
setsid();将当前改变为新会话的组长
3.改变子进程的工作目录
//因为子进程创建通常使用和父进程相同的路径
chdir("/etc");//存放到根目录下的etc中
4.修改子进程的文件操作掩码
/补充:默认掩码:0002
umask(0);
5.关闭所有的文件文件描述符
close(0);stdin
close(1);stdou
close(2);stderr
5.进程间的通信
1.管道通信
1.
6.线程
线程的概念:
比进程更小,称为轻量级进程。线程依赖于进程。
线程没有独立的内存空间。线程之间共享进程的共享区域。
1.线程的创建
函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
参数1:线程id
参数2:创建线程的属性,通常为NULL
参数3:线程执行的任务,是一个函数指针
参数4:线程的参数,通常为NULL
2.线程的关闭
void pthread_exit(void *retval);
参数:表示线程的结束状态;与进程的status类似
3.线程的等待
int pthread_join(pthread_t thread, void **retval)
参数1:等待哪个线程结束
参数2:因为在关闭线程时,使用的一级指针,要想存储一级指针就得使用二级指针。
存储线程结束状态的地址
4.指定某个线程结束
int pthread_cancel(pthrea_t thread)
参数:要结束的线程
5.互斥锁
有效避免在同一时刻,多个线程竞争一个资源。
原理:通过信号量,信号量的值只能是0或1。当信号量为1时,表示可以访问资源。为0时,则等待资源。
补充:进程和线程区别:
进程:拥有执行的完整独立的资源,进程的通信依靠不同的方法
线程:被称为轻量级进程,有自己的线程id,线程会共用进程打开的文件,线程通信在进程中,依靠共享区域
主线程每秒都在变化,子线程获取主线程变化的值
与线程的区别:线程共享资源
线程 之间是 共享的,进程 之间是独立的。