Linux下IO的系统调用接口 文件流指针 文件描述符 文件重定向
Linux下ext2文件系统 inode节点 软链接/硬链接 动态库和静态库的生成
1.系统调用接口: open、close、write、read、lseek
库函数和系统调用函数的关系:上下级的调用关系
int open(const char* pathname, int flags, mode_t mode);
pathname : 文件名称
flags: 选项标志
必选项:O_RDONLY只读 O_WRONLY只写 O_RDWR读写
可选项:O_CREAT:若文件不存在则创建,存在则打开
O_EXCL:搭配O_CREAT使用,若文件存在则报错返回
O_APPEND:追加写
代码演示库函数:
/*c库中的io接口
* fopen fclose fwrite fread fseek
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main()
{
//mode: r r+ w w+ a a+
//r:只读 r+:读写
//w:只写 w+:读写 若文件不存在则创建,若问阿金存在则清空内容
//a:写追加 a+:读写打开,追加写,从起始位置读,文件不存在则创建
FILE* fp = fopen("./test.txt", "w+");
if(fp == NULL){
printf("fopen error]n");
return-1;
}
char* ptr = "hello bit!!\n";
//size_t fwrite(void* ptr, size_t size, size_t nmemb, FILE* stream);
fseek(fp, 5, SEEK_END);
if(fwrite(ptr, 1, strlen(ptr), fp) != strlen(ptr)){
printf("fwirte error\n");
}
//int fseek(FILE* stream, long offset, int whence);
//whence:
//SEEK_SET 文件起始位置
//SEEK_CUR 文件读写位置
//SEEK_END 文件末尾位置
fseek(fp, 0, SEEK_SET);
//size_t fread(void* ptr, size_t size, size_t nmmeb, FILE* stream);
char buf[1024] = { 0 };
int ret = fread(buf, 1, 1024-1, fp);
if(ret < 0){
printf("read error");
}
printf("ret:%d buf:%s\n", ret, buf);
fclose(fp);
return 0;
}
代码演示系统调用接口:
/*演示基础io系统调用接口demo
* open close read write lseek
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
int main()
{
//将当前进程文件创建权限掩码设置为0(仅对当前进程有效)
//mode_t umask(mode_t mask);
umask(0);
//int open(const char* pathname, int flags, mode_t mode);
//pathname:文件名称
//flags:选项标志
// 必选项:
// O_RDONLY 只读
// O_ERONLY 只写
// O_RDWR 读写
// 可选项:
// O_CREAT 若文件不存在则创建,存在则打开
// O_EXCL 搭配O_CREAT使用,若文件存在则报错返回
// O_TRUNC 打开文件同时清空文件内容
// O_APPEND 向文件中追加内容
//mode:权限(通常使用八进制数据表示)
//返回值:文件描述符(非负整数) 失败:-1
int fd = open("./test.txt", O_CREAT|O_RDWR|O_TRUNC|O_APPEND, 0664);
if(fd < 0){
perror("open error");
return -1;
}
//ssize_t write(int fd, const void* buf, size_t count);
//fd:文件描述符
//buf:要写入的数据
//count:要写入的数据长度
//返回值:实际写入长度 失败:-1
char* ptr = "hallo bit~~!";
ssize_t ret = write(fd, ptr, strlen(ptr));
if(ret < 0){
perror("write");
return -1;
}
//off_t lseek(int fd, off_t offset, int whence);
//跳转文件读写位置到whence位置偏移offset个字节
//whence:SEEK_SET SEEK_CUR SEEK_END
lseek(fd, 0, SEEK_SET);
char buf[1024] = { 0 };
//ssize_t read(int fd, void* buf, size_t count);
//返回值:实际的读取字节长度 失败;-1
ret = read(fd, buf, 1023);
if(ret < 0){
perror("read error");
return -1;
}else if(ret == 0){
printf("end of file!\n");
}
printf("buf:[%s]\n",buf);
//关闭文件
close(fd);
return 0;
}
2.文件描述符:非负整数
凭什么一个整数能够操作文件?
一个进程中会打开很多文件,那么进程就需要对这些文件进行管理(描述+组织)
描述:结构体 组织:结构体数组
进程通过file结构体描述文件,并且使用结构体数组将这些描述符组织起来进行管理,我们打开文件所获取的文件描述符其实就是这个数组中对应文件描述信息的下标。我们用户通过下标找到文件描述信息,进而对文件进行操作。
文件流指针与文件描述符关系:
文件流指针这个结构体中封装了文件描述符作为成员变量。
文件缓冲区:
向文件写入数据是先写入缓冲区中,等缓冲区满了,才一次性写入文件;缓冲区在库函数的文件流指针中,系统调用中没有。
普通文件不具备换行刷新缓冲区的功能,只有标准输出文件才有这个功能
文件描述符的分配规则:
遵循最小使用原则,一个进程运行直接打开三个文件(标准输入0、标准输出1、标准错误2)
文件重定向:dup2(oldfd, newfd)
本质:改变描述符下标所对应的文件描述信息
简单的minishell的实现:
/*编写建议的minishell,实现能够运行shell的 外部命令
* 1.读取缓冲区
* 2.解析输入
* 3.创建子进程
* 4.程序替换
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
int main()
{
char buf[1024] ={ 0 };
while(1){
printf("[sun@sun~]:");
fflush(stdout);//刷新标准输出缓冲区--将缓冲区的数据直接打印
memset(buf, 0x00, 1024);
//%[^\n] scanf取数据的时候遇到各种空白字符就会停止读取
// 为了读取所有的输入,让scanf遇到换行的时候再终止
//%*c scanf取数据之后,缓冲区中遗留换行符,取不出来,导致
// scanf非阻塞,陷入死循环,所以取出一个字符并丢弃
//scanf返回值读取数据的个数,如果读取失败,避免缓冲区的换行取不出来
//导致死循环(防备直接回车情况)
if(scanf("%[^\n]%*c", buf) != 1){
getchar();
continue;
}
//ls -l >> a.txt
char* str = buf;
int redirect_flag = 0;//0-清空 1-追加
char* redirect_file = NULL;
int fd = -1;
while(*str != '\0'){
//如果当前字符是重定向符号,则进入判断,将整个命令从这里截断
//前边是命令,后边是重定向信息
if(*str == '>'){
*str = '\0';
//判断重定向方式
redirect_flag++;
if(*(str+1) == '>'){
redirect_flag++;
}
str += redirect_flag;
//走完文件名前边的空白字符
while(isspace(*str) && *str != '\0'){
*str++ = '\0';
}
//文件名的起始位置
redirect_file = str;
//走完文件名
while(!isspace(*str) && *str != '\0'){
str++;
}
//文件名最后需要结束符
*str = '\0';
continue;
}
str++;
}
//ls -l -a
char *ptr = buf;
int argc = 0;
char *argv[32];
while(*ptr != '\0')
{
if(!isspace(*ptr))
{
argv[argc++] = ptr;
//将ls读完,指向空白字符处
while(*ptr != '\0' && !isspace(*ptr))
{
++ptr;
}
}
//将空白字符全部替换为字符串结尾标志
*ptr = '\0';
++ptr;
}
//参数结尾必须为空
argv[argc] = NULL;
//创建子进程
int pid = fork();
if(pid < 0){
perror("fork error");
return -1;
}else if(pid == 0){
//子进程程序替换,运行指定命令
if(redirect_flag == 1){
//清空重定向
fd = open(redirect_file, O_CREAT|O_WRONLY|O_TRUNC, 0664);
//这个重定向不能再父进程中完成,因为一旦父进程重定向了,
//shell程序后续的界面就打印不出来了,都会写入到这个文件中
dup2(fd, 1);
}else if(redirect_flag == 2){
//追加重定向
fd = open(redirect_file, O_CREAT|O_WRONLY|O_APPEND, 0664);
dup2(fd, 1);
}
execvp(argv[0],argv);
exit(0);
}
//避免僵尸子进程
waitpid(pid, NULL, 0);
}
return 0;
}
3.文件系统:管理磁盘文件(以linux下ext2为例)
文件系统下是按块分配的,一块大小是4k,其中有一个超级块(里面有文件系统的类型,块个数,块已使用数目和未使用数目)
文件如果大于4k,文件系统分配块时,不是顺序分配,是根据inode节点找到一个文件的完整信息
超级快,inode-bitmap,data-bitmap,inode,data
inode节点(大小、权限、用户、时间、文件的存储地址)(查看inode节点号:ls -i)
普通文件和目录文件有什么不同?
目录也是文件只不过数据存储的是目录下的文件名和inode节点号(最主要的),文件名和inode节点--->目录项
查看文件内容:cat file 流程:通过文件名在目录项中获取文件inode节点->通过inode节点找到数据存储位置->读取文件数据
4.软链接和硬链接
创建硬链接:ln 原文件名 硬链接文件名
创建软链接:ln -s 原文件名 软链接文件名
软链接和硬链接的区别:
1.软连接类似于文件的快捷方式的文件,硬链接是一个文件的别名
2.硬链接文件具有相同的inode节点号,所有的硬链接文件通过inode操作数据;软链接文件和源文件不是同一个文件,拥有不同的inode节点号,访问软链接文件,就是通过软链接文件中保存的源文件路径访问源文件
3.删除源文件,软链接时效,但是硬链接不影响,只是链接数-1(当一个文件链接数为0时才真正的删除一个文件,否则只是删除目录项))
4.软链接可以针对目录创建,但是硬链接不可以
5.软链接可以跨分区创建,硬链接不可以
df命令查看分区挂载情况;fdisk -l查看系统磁盘;du查看文件在磁盘所占大小
linux下SATA接口类型硬盘命令方式:sda sdb...;sda这块磁盘进行分区:sda1,sda2...
5.动态库/静态库
如何生成动态库:
gcc -fPIC -c b.c -o b.o (-fPIC:产生位置无关代码)
gcc --share b.o -o libmy_b.so (my_b是自己命名的,其余的是动态库的形式)
如何生成静态库:
ar -cr libmy_b.a b.o
静态库命名:以lib为前缀,.a为后缀,中间是库名称
动态库命名:以lib为前缀,.so为后缀,中间是库名称
gcc选项: -E -S -c -o -static -g
-fPIC:生成动态库需要---产生位置无关代码
--share:生成共享库(动态库)而不是可执行程序
ar:静态库链接器---用于生成静态库
-c:创建静态库
-r:模块替换
动态库/静态库的链接
gcc a.c -o -a lmy_b
生成可执行程序时,系统需要所有库都在指定路径(/lib64)下,这时候才能找到这个库
如果不想将库放置到/lib64下,这时候可以指定库的查找路径
gcc a.c -o a -L ./ -my_b
-L 指定库的搜索路径(默认路径找不到库就会到这里来)
-l 指定要链接的库名称
动态链接生成的可执行程序,需要动态库的存在并且存在于指定路径下,因此动态库是最好直接拷贝到/lib64下
动态库不但链接的时候需要指定库的路径,运行时候也需要;但是静态库只是链接的时候需要就可以了,运行的时候并不依赖静态库