基础IO:输入输出----
学习内容:
回顾标准库IO接口:FILE*----文件流指针
系统调用IO接口学习:int----文件描述符
文件描述符和文件流指针之间的关系
文件描述符到底是什么----凭什么操作文件
文件系统----Linux下的ext2
软链接/硬链接
静态库/动态库:生成+使用
回顾标准库IO接口:
fopen fclose fread fwrite fseek fgets
printf fprintf sprintf
系统调用IO接口学习:
open close write read lseek
open
- int open(const char *pathname,int flags,mode_t mode);
- pathname : 文件名
- flags : 选项标志
- 只能选其一/也必选其一
1、 O_RDONLY 只读
2、 O_WRONLY 只写
3、 O_RDWR 读写
- 可选:
1、O_CREAT 文件不存在则创建,存在则打开
2、O_TRUNC 打开文件的时候清空原有内容
3、O_APPEND 写数据的时候总是追加在文件末尾
- mode 文件权限
S_IWUSR | S_IXUSR | S_IRWXG
0064
- 返回值:文件描述符(正整数) 失败:-1
write
ssize_t write(int fd,const void *buf,size_t count);
- fd:打开文件返回的操作句柄
- buf:要写入的数据
- count:要写入的数据长度
- 返回值:实际的写入长度 失败:-1
read
- ssize_t read(int fd, void *buf, size_t count);
- fd:打开文件返回的操作句柄—文件描述符
- buf:读到数据放到buf中
- count:要读取的数据长度
- 返回值:实际读到的数据长度 失败:返回-1
lseek
- off_t lseek(int fd, off_t offset, int whence);
- fd:打开文件返回的操作句柄—文件描述符
- offset:偏移量
- whence:偏移起始位置
SEEK_SET 文件起始位置
SEEK_CUR 文件当前读写位置
SEEK_END 文件末尾位置
- 返回值:偏移的位置到文件起始位置的偏移量
close
练习
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
int main()
{
//open打开文件,有就打开,没有就创建
int fd;
char *file="./tmp.txt";
umask(0);
fd=open(file,O_RDWR | O_CREAT,0664);
if(fd<0)
{
perror("open error!\n");
return -1;
}
lseek(fd,10,SEEK_END);
//write
char buf[1024]="怎么才能成为一名优秀的程序员?\n";
int ret=write(fd,buf,strlen(buf));
if(ret<0)
{
perror("write error!\n");
}
lseek(fd,0,SEEK_SET);
//read
memset(buf,0x00,1024);
ret=read(fd,buf,1023);
if(ret<0)
{
perror("read error!\n");
return -1;
}
else if(ret==0)
{
printf("没有读到数据!\n");
return -1;
}
printf("buf:[%d-%s]\n",ret,buf+30);
close(fd);
return 0;
}
文件描述符和文件流指针之间的关系
文件流指针是标准库操作句柄:FILE
文件描述符是系统调用接口句柄:int
库函数和系统调用接口的关系?–库函数封装了系统调用接口
fp=fopen—>fwrite(fp) write(fp)
printf(“helli”)—>hello先被写入缓冲区
标准输入 | 标准输入 | 标准错误 | |
---|---|---|---|
stdin | stdout | stderr | —文件流指针 |
0 | 1 | 2 | —文件描述符 |
STDIN_FILENO | STDOUT_FILENO | STDERR_FILENO | —文件描述符的宏 |
文件流指针中包含了文件描述符这么个成员变量
函数库内部封装的就是系统调用接口
缓冲区是用户态的缓冲区,存在于文件流指针结构体中
文件描述符到底是什么----凭什么操作文件
文件描述符是内核中 strcut file * fd_array[] 数组下标,这个数组中存放的就是文件描述信息
文件描述符分配规则:最小未使用
重定向:重定向的是文件描述符,改变一个文件描述符所对应的文件描述信息
改变了文件描述符所对应的文件描述信息,这时候对描述符写入数据,数据就从原本要写入的文件,流向了新的文件
\n刷新缓冲区仅仅针对的是标准输出文件,对于其它磁盘文件并不具备刷新缓冲区效果
系统调用因为没有缓冲区,因此不会执行缓冲区动作
minishell中的重定向实现:
在原有字符串及解析完毕的情况字;通过[ls -l > a/txt]解析之后获取到
argv[]={“ls” , “-l” , “>” , “a.txt” , NULL}
在子进程中判断argv中是否包含>或>>来决定是否需要重定向,以及重定向方式(>清空,>>追加);
fd=open(O_TRUNC | O_APPEND)
dup2(fd,1)将标准输出重定向到指定文件
/* 自主minishell实现
* 1. 获取标准输入
* 2. 解析输入得到[命令明]+[运行参数]
* 3. 创建子进程
* 子进程中进行程序替换
* 4. 进程等待
* */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
char buf[1024] = {0};
int do_face()
{
memset(buf, 0x00, 1024);
printf("[user@localhost]$ ");
fflush(stdout);
// ls -l\n
//%[^\n] 从缓冲区取数据的时候,遇到\n为止:[ ls -l]
//经过这一步之后,缓冲区中留下了\n,但是\n取不出来,导致scanf非阻塞
//但又取不出最后的\n字符
//%*c 从缓冲区中取出一个字符(丢弃)
if (scanf("%[^\n]%*c", buf) != 1) {
getchar();
return -1;
}
return 0;
}
int argc = 0;
char *argv[32];
int do_parse()
{
//[ ls -l ] ---> [ls] + [-l]
argc = 0;
char *ptr = buf;
while(*ptr != '\0') {
//将指针走到非空白字符处 l -
if (!isspace(*ptr)) {
argv[argc] = ptr;
argc++;
//将ls走完,认为ls是一个完整的字符串
//不能仅仅将l字符串作为第0个参数
while(!isspace(*ptr) && *ptr != '\0') {
ptr++;
}
//在ls之后添加一个字符串结尾标志
//因为我们向获取的是ls,而不是[ls -l ]
*ptr = '\0';
}
ptr++;
}
//argv[] = {"ls", "-l", ">", "a.txt", NULL }
argv[argc] = NULL;
return 0;
}
int main()
{
while(1) {
//获取标准输入,显示shell提示
if (do_face() < 0) continue;
//对字符串进行解析,去掉空格,将字符串解析成字符串指针数组
//[ ls -l >> a.txt ] ->
//argv[] = {ls, -l, >>, a.txt}
if (do_parse() < 0) continue;
//实现shell内建命令
if (strcmp(argv[0], "cd") == 0) {
// int chdir(const char *path);
// 改变当前工作路径
chdir(argv[1]);
continue;
}
int pid = fork();
if (pid < 0) {
perror("fork error");
return -1;
}else if (pid == 0) {
//重定向不能在父进程中完成,父进程是需要打印数据的
int i;
//ls -l > a.txt
//argv[] = {ls , -l , NULL , a.txt}
for (i = 0; i < argc; i++) {
if (strcmp(argv[i], ">") == 0) {
int fd = open(argv[i+1], O_WRONLY|O_CREAT|O_TRUNC);
dup2(fd, 1);
argv[i] = NULL;
}else if (strcmp(argv[i], ">>") == 0) {
int fd = open(argv[i+1], O_WRONLY|O_CREAT|O_APPEND);
dup2(fd, 1);
argv[i] = NULL;
}
}
execvp(argv[0], argv);
exit(-1);
}
wait(NULL);
}
return 0;
}
文件系统:
超级块,inode table,数据区域,inode_bitmap,data_bitmap
inode(超级块): fsize,fmode,user,time,addr
存储一个文件流程: 从data_bitmap中获取空间数据块,写入文件数据,从inode_bitmap中获取空间inode节点,写入文件详细信息以及数据块位置;将文件目录项写入所在的目录文件中
目录文件中存放的是一张表: 目录下的文件信息(目录项:文件名+inode节点号)
读取文件流程: cat a.txt,通过文件名在目录项中获取到inode节点号,进而在inode区域中通过inode节点号获取到inode节点,进而获取到文件数据块存储位置,进而读取到文件数据
硬链接/软链接
软链接文件(独立文件): 是一个单独文件,通过记录源文件路径进而访问到源文件
硬链接文件(文件别名): 跟源文件没区别,通过与所有文件名使用相同的inode节点访问到文件数据
创建:
ln tmp.txt tmp.hard 给源文件创建硬链接文件
ln -s tmp.txt tmp.soft 给源文件创建软链接文件
删除源文件,硬链接文件,连接数-1;软链接文件失效
软链接文件可以跨分区建立,硬链接不可以
软链接文件可以对目录创建,硬链接不可以
动态库和静态库:生成+使用
gcc -c b.c -o b.o
库文件: 打包了大量机器代码的文件–供别人的程序使用
生成动态库: 动态库命名:lib是前缀 .so是后缀 中间是库名称
gcc -fPIC -c b.c -o b.o 将每个c语言代码编译成自己的目标代码
gcc --share b.o -o libmytest.so 将所有的目标代码合到一起生成动态库
fPIC:编译选项–产出位置无关代码
–share:链接选项–将所有的目标代码链接到一起生成动态库而不是生成可执行程序
生成静态库: 静态库命名: lib是前缀 .a是后缀 中间是库名称
gcc -c b.c -o b.o 将每个c语言代码编译成自己的目标代码
ar -cr libmytest.a b.o 将所有的目标代码合到一起生成静态库
ar:打包静态库的命令 -c创建 -r模块替换
使用静态库: 链接静态库使,不需要静态编译,只需要将库文件放到指定路径下,设置一下搜索路径即可,在linux下库文件只有被放入到指定路径下,才能被找到:/lib/lib64 /usr/lib /usr/lib64
1.向/etc/ld.so.conf.d/添加配置文件,在配置文件中添加库的搜索路径
2.将库文件直接放到指定路径下
3.设置环境变量:LIBRARY_PATH 库的链接搜索路径环境变量
设置环境变量:LD_LIBARARY_PATH 程序运行时库的加载路径环境变量
4.使用gcc的-L选项指定库的链接搜索路径gcc -L 用于指定库的链接搜索路径
-l指定链接库名称
gcc a.c -o main -L.-lmytest 命令 -ldd 查找库
通常用户链接一个操作系统没有自带的库,都会链接静态库,用户喜欢使用-L指定库的链接搜索路径,因为程序链接静态库不需要依赖静态库存在,因此可以不用拷贝到/lib下因为没有其他程序使用这个第三方库,因此也不用考虑代码的冗余问题
链接静态库不需要使用静态链接-static,只需要保证指定路径下只有静态库就可以;链接器优先去指定的路径下链接库