目录
预备知识
什么叫做当前路径
strlen(s) + 1问题
cat代码实现
#include <stdio.h>
#include <unistd.h>
#include <string.h>
// mytextfile + 一个文件名
int main(int argc, char* argv[])
{
if (argc != 2)
{
printf("argv error!\n");
return 1;
}
FILE* fp = fopen(argv[1], "r");
if (fp == NULL)
{
perror("fopen");
return 2;
}
// 按行读取
char line[64];
// fgets -> C -> s(string) -> 会自动在字符结尾添加\0
while (fgets(line, sizeof(line), fp) != NULL)
{
// printf("%s", line);
fprintf(stdout, "%s", line);
}
fclose(fp);
return 0;
}
文件写入的系统接口细节
模拟实现传递标志位
// 用int中的不重复的一个bit,就可以标识一种状态
#define ONE 0x1 //0000 0001
#define TWO 0x2 //0000 0010
#define THREE 0x4 //0000 0100
void show(int flags) //0000 0101
{
if(flags & ONE) printf("hello one\n"); // 0000 0011 & 0000 0001
if(flags & TWO) printf("hello two\n");
if(flags & THREE) printf("hello three\n");
}
int main()
{
show(ONE);
printf("-----------------------------------------\n");
show(TWO);
printf("-----------------------------------------\n");
show(ONE | TWO); //000 0001 | 0000 0010 = 0000 0011
printf("-----------------------------------------\n");
show(ONE | TWO | THREE);
printf("-----------------------------------------\n");
show(ONE | THREE);
printf("-----------------------------------------\n");
return 0;
}
int main()
{
/// 系统接口open
//int fd = open("log.txt", O_RDONLY); .// 等价于 fopen("log.txt", "r")
//int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666); // 等价于 fopen("log.txt", "w")
/// mode 可以设置文件权限 0666 表示 rw-rw-rw- 因为有umask会覆盖一些权限 所以需要umask(0)
umask(0);
int fd = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); // 等价于fopen("log.txt", "a");
printf("open success, fd: %d\n", fd);
if(fd < 0)
{
perror("open");
return 1;
}
/// 系统接口read
char buffer[64];
memset(buffer, '\0', sizeof(buffer));
read(fd, buffer, sizeof(buffer));
/// 系统接口write
const char *s = "hello write\n";
const char *s = "aa\n";
write(fd, s, strlen(s)); //要不要+1
/// 系统接口close
close(fd);
return 0;
}
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd1 = open("log1.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
printf("open success, fd: %d\n", fd1);
int fd2 = open("log2.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
printf("open success, fd: %d\n", fd2);
int fd3 = open("log3.txt", O_WRONLY|O_CREAT|O_APPEND, 0666); //rw-rw-rw-
printf("open success, fd: %d\n", fd3);
close(fd1);
close(fd2);
close(fd3);
return 0;
}
文件系统函数
open
函数原型:
当文件存在时
int open(const char* pathname,int flags)
当文件不存在时
int open (const char* pathname,int flags,int perms)
flags
主模式:
O_RDONLY:只读模式
O_WRONLY:只写模式
O_RDWR:读写,模式
副模式:
O_CREAT:当文件不存在,需要去创建文件
O_APPEND:追加模式
O_DIRECT:直接IO模式
O_SYNC:同步模式
O_NOBLOCK:非阻塞模式
O_EXCL:如果同时使用O_CREAT而且该文件又已经存在时,则返回错误, 用途:以防止多个进程同时创建同一个文件
O_TRUNC:若文件存在,则长度被截为0,属性不变
返回值:
成功:文件描述符
失败:-1
close
函数原型:
int close(int fd)
返回值:
成功:0
失败:-1
read
函数原型:
ssize_t read(int fd,void *buff,size_t count) // ssize_t是有符号数
1
返回值:
成功:
count:成功读取全部字节
0~count:
剩余文件长度小于count
读取期间被异步信号打断
失败:
-1,读取错误
write
ssize_t write(int fd,void *buff,size_t count)
1
返回值:
成功:
count:成功读取全部字节
0~count:
写入期间被异步信号打断
失败:
-1,读取错误
lseek
功能:
设置文件读写位置
函数原型:
off_t lseek(int fd,off_t offset,int whence)
若whence为SEEK_SET,基准点为文件开头
若whence为SEEK_CUR,基准点为当前位置
若whence为SEEK_END,基准点为文件末尾
返回值:
成功:文件偏移位置值
失败:-1
读写是使用的同一个位置指针。使用O_RDWR时,如果先读了一段再写,那么写的位置从读完处开始,同理先写了一段再读,
那么读的位置从写完处开始。可以使用lseek来改变文件位置指针,即读写的位置。
sync
fd理解
重定向的本质
// 输出重定向(从显示器到文件)
int main()
{
close(1);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if(fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
printf("fd: %d\n", fd);
fprintf(stdout, "hello fprintf\n");
const char *s = "hello fwrite\n";
fwrite(s, strlen(s), 1, stdout);
fflush(stdout);
close(fd);
return 0;
}
/// dup2实现: 输出重定向
// mytextflie + 内容
// ./mytextfile hello 会将hello写入到stdout文件
int main(int argc, char* argv[])
{
if (argc != 2)
{
return 1;
}
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0)
{
perror("open");
return 1;
}
dup2(fd, 1);
fprintf(stdout, "%s\n", argv[1]); //stdout->1->显示器
close(fd);
return 0;
}
// 输入重定向(从键盘重定向到文件)
int main()
{
// 本来要从键盘读取内容,会从log.txt文件读取内容,并保存到buffer中
close(0);
int fd = open("log.txt", O_WRONLY | O_APPEND | O_CREAT);
if (fd < 0)
{
perror("open");
return 1;
}
printf("fd: %d\n", fd);
char buffer[64];
fgets(buffer, sizeof buffer, stdin);
printf("%s\n", buffer);
return 0;
}
问题
- 文件描述符的理解
在进程中每打开一个文件,都会创建有相应的文件描述信息struct file,这个描述信息被添加在pcb的struct files_struct中,以数组的形式进行管理,随即向用户返回数组的下标作为文件描述符,用于操作文件 - 请简述重定向的实现原理
每个文件描述符都是一个内核中文件描述信息数组的下标,对应有一个文件的描述信息用于操作文件,而重定向就是在不改变所操作的文件描述符的情况下,通过改变描述符对应的文件描述信息进而实现改变所操作的文件
缓冲区
简易的自制缓冲区
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#include <stdlib.h>
#define NUM 1024
struct MyFILE_ {
int fd;
char buffer[1024];
int end; //当前缓冲区的结尾
};
typedef struct MyFILE_ MyFILE;
MyFILE* fopen_(const char* pathname, const char* mode)
{
assert(pathname);
assert(mode);
MyFILE* fp = NULL;
if (strcmp(mode, "r") == 0)
{
}
else if (strcmp(mode, "r+") == 0)
{
}
else if (strcmp(mode, "w") == 0)
{
int fd = open(pathname, O_WRONLY | O_TRUNC | O_CREAT, 0666);
if (fd >= 0)
{
fp = (MyFILE*)malloc(sizeof(MyFILE));
memset(fp, 0, sizeof(MyFILE));
fp->fd = fd;
}
}
else if (strcmp(mode, "w+") == 0)
{
}
else if (strcmp(mode, "a") == 0)
{
}
else if (strcmp(mode, "a+") == 0)
{
}
else {
//什么都不做
}
return fp;
}
//是不是应该是C标准库中的实现!
void fputs_(const char* message, MyFILE* fp)
{
assert(message);
assert(fp);
strcpy(fp->buffer + fp->end, message); //abcde\0
fp->end += strlen(message);
//for debug
printf("%s\n", fp->buffer);
//暂时没有刷新, 刷新策略是谁来执行的呢?用户通过执行C标准库中的代码逻辑,来完成刷新动作
//这里效率提高,体现在哪里呢??因为C提供了缓冲区,那么我们就通过策略,减少了IO的执行次数(不是数据量)
if (fp->fd == 0)
{
//标准输入
}
else if (fp->fd == 1)
{
//标准输出
if (fp->buffer[fp->end - 1] == '\n')
{
//fprintf(stderr, "fflush: %s", fp->buffer); //2
write(fp->fd, fp->buffer, fp->end);
fp->end = 0;
}
}
else if (fp->fd == 2)
{
//标准错误
}
else
{
//其他文件
}
}
void fflush_(MyFILE* fp)
{
assert(fp);
if (fp->end != 0)
{
//暂且认为刷新了--其实是把数据写到了内核
write(fp->fd, fp->buffer, fp->end);
syncfs(fp->fd); //将数据写入到磁盘
fp->end = 0;
}
}
void fclose_(MyFILE* fp)
{
assert(fp);
fflush_(fp);
close(fp->fd);
free(fp);
}
int main()
{
//close(1);
MyFILE* fp = fopen_("./log.txt", "w");
if (fp == NULL)
{
printf("open file error");
return 1;
}
fputs_("one: hello world", fp);
fork();
fclose_(fp);
}
minishell支持缓冲区
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
#define NUM 1024
#define SIZE 32
#define SEP " "
//保存完整的命令行字符串
char cmd_line[NUM];
//保存打散之后的命令行字符串
char *g_argv[SIZE];
// 写一个环境变量的buffer,用来测试
char g_myval[64];
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
#define NONE_REDIR 0
int redir_status = NONE_REDIR;
char *CheckRedir(char *start)
{
assert(start);
char *end = start + strlen(start) - 1; //ls -a -l\0
while(end >= start)
{
if(*end == '>')
{
if(*(end-1) == '>')
{
redir_status = APPEND_REDIR;
*(end-1) = '\0';
end++;
break;
}
redir_status = OUTPUT_REDIR;
*end = '\0';
end++;
break;
//ls -a -l>myfile.txt
//ls -a -l>>myfile.txt
}
else if(*end == '<')
{
//cat < myfile.txt,输入
redir_status = INPUT_REDIR;
*end = '\0';
end++;
break;
}
else{
end--;
}
}
if(end >= start)
{
return end; //要打开的文件
}
else{
return NULL;
}
}
// shell 运行原理 : 通过让子进程执行命令,父进程等待&&解析命令
int main()
{
extern char**environ;
//0. 命令行解释器,一定是一个常驻内存的进程,不退出
while(1)
{
//1. 打印出提示信息 [whb@localhost myshell]#
printf("[root@我的主机 myshell]# ");
fflush(stdout);
memset(cmd_line, '\0', sizeof cmd_line);
//2. 获取用户的键盘输入[输入的是各种指令和选项: "ls -a -l -i"]
// "ls -a -l>log.txt"
// "ls -a -l>>log.txt"
// "ls -a -l<log.txt"
if(fgets(cmd_line, sizeof cmd_line, stdin) == NULL)
{
continue;
}
cmd_line[strlen(cmd_line)-1] = '\0';
// 2.1: 分析是否有重定向, "ls -a -l>log.txt" -> "ls -a -l\0log.txt"
//"ls -a -l -i\n\0"
char *sep = CheckRedir(cmd_line);
//printf("echo: %s\n", cmd_line);
//3. 命令行字符串解析:"ls -a -l -i" -> "ls" "-a" "-i"
// export myval=105
g_argv[0] = strtok(cmd_line, SEP); //第一次调用,要传入原始字符串
int index = 1;
if(strcmp(g_argv[0], "ls") == 0)
{
g_argv[index++] = "--color=auto";
}
if(strcmp(g_argv[0], "ll") == 0)
{
g_argv[0] = "ls";
g_argv[index++] = "-l";
g_argv[index++] = "--color=auto";
}
//?
while(g_argv[index++] = strtok(NULL, SEP)); //第二次,如果还要解析原始字符串,传入NULL
if(strcmp(g_argv[0], "export") == 0 && g_argv[1] != NULL)
{
strcpy(g_myval, g_argv[1]);
int ret = putenv(g_myval);
if(ret == 0) printf("%s export success\n", g_argv[1]);
//for(int i = 0; environ[i]; i++)
// printf("%d: %s\n", i, environ[i]);
continue;
}
//for debug
//for(index = 0; g_argv[index]; index++)
// printf("g_argv[%d]: %s\n", index, g_argv[index]);
//4.内置命令, 让父进程(shell)自己执行的命令,我们叫做内置命令,内建命令
//内建命令本质其实就是shell中的一个函数调用
if(strcmp(g_argv[0], "cd") == 0) //not child execute, father execute
{
if(g_argv[1] != NULL) chdir(g_argv[1]); //cd path, cd ..
continue;
}
//5. fork()
pid_t id = fork();
if(id == 0) //child
{
if(sep != NULL)
{
int fd = -1;
//说明命令曾经有重定向
switch(redir_status)
{
case INPUT_REDIR:
fd = open(sep, O_RDONLY);
dup2(fd, 0);
break;
case OUTPUT_REDIR:
fd = open(sep, O_WRONLY | O_TRUNC | O_CREAT, 0666);
dup2(fd, 1);
break;
case APPEND_REDIR:
//TODO
fd = open(sep, O_WRONLY | O_APPEND | O_CREAT, 0666);
dup2(fd, 1);
break;
default:
printf("bug?\n");
break;
}
}
// printf("下面功能让子进程进行的\n");
// printf("child, MYVAL: %s\n", getenv("MYVAL"));
// printf("child, PATH: %s\n", getenv("PATH"));
//cd cmd , current child path
//execvpe(g_argv[0], g_argv, environ); // ls -a -l -i
//不是说好的程序替换会替换代码和数据吗??
//环境变量相关的数据,会被替换吗??没有!
execvp(g_argv[0], g_argv); // ls -a -l -i
exit(1);
}
//father
int status = 0;
pid_t ret = waitpid(id, &status, 0);
if(ret > 0) printf("exit code: %d\n", WEXITSTATUS(status));
}
}
缓冲区的行刷新/全刷新一份代码
stdout 和 stderr 区别和联系
软硬链接
动静态库
静态库的制作
静态库的使用
动态库的制作
动态库的使用