open()函数
int open (count char* pathname, int flags)
int open (count char* pathname, int flags,mode_t made);
pathname:要打开或创建的目标文件
flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags
参数:
O_RDONLY:只读
O_WRONLY:只写
O_RDWR :读写 O_RDWR = O_WDONLY&O_RDONLY
这三个常量,必须指定一个切只能指定一个
O_CREAT :若这个文件不存在,则创建它。此时需要使用mode选项,来知名新文件的访问权限
O_APPEND:追加写
返回值:
成功:新打开的文件描述符
失败:-1
open函数具体使用哪个,和具体应用场景有关,如目标文件不存在,则需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。
创建文件有权限屏蔽。不出现umask屏蔽的权限。
umask :在创建文件时,屏蔽掉umask中间出现的权限
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main ()
{
umask(0);//创建文件时有文件屏蔽
int fd = open("myfile.txt",O_WRONLY | O_CREAT,0644);
if(fd<0){
printf("open error\n");
return 1;
}
close(fd);
return 0;
}
打开一个文件myfile.txt,而我们当前路径下没有这个文件,就创建这个文件,权限设置为0644,创建成功之后立马关闭这个文件。
write()函数
ssize_t write(int fd, const void *buf,size_t count)
第一个参数:文件描述符
第二个参数:写入的字符串的首地址或者缓冲区
第三个参数:期望写入的字节数
返回值:实际写入的字节数。
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main ()
{
umask(0);//创建文件时有文件屏蔽
int fd = open("myfile.txt",O_WRONLY | O_CREAT,0644);
if(fd<0){
printf("open error\n");
return 1;
}
int i = 0;
const char *msg = "Hello bit\n";
while(i < 10){
write(fd,msg,strlen(msg));//strlen()函数在C语言中跟linux中的值不同(在linux中少1)
i++;
}
close(fd);
return 0;
}
打开一个文件myfile.txt,而我们当前路径下没有这个文件,就创建这个文件,权限设置为0644,创建成功之后写入10行字符串“Hello bit”,写入成功之后立马关闭这个文件。
read()函数
第一个参数:文件描述符
第二个参数:读数据放入的空间
第三个参数:期望读的字节数
返回值大于0 成功;小于0失败;等于0表示读到文件末尾
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>
#include<string.h>
int main ()
{
int fd = open("myfile.txt",O_RDONLY);//此时的file是write过的,里面存有数据(10行Hello bit)
if(fd<0){
printf("open error\n");
return 1;
}
char buf[1024];
ssize_t s = read(fd,buf,sizeof(buf)-1);//buf在C语言中以字符串形式呈现,必须以\0结尾
if(s > 0){
buf[s] = 0;//加上\0
printf("%s",buf);
}
close(fd);
return 0;
}
open()打函数开已经写好的文件myfile.txt,再用read()函数读取里面的内容,最后关闭文件。
close ()函数
close()函数用来关闭一个你打开的文件
参数是文件描述符。
纵向对比文件描述符fd与FILE结构体
在linux中,每一个进程中都有一个task_struct结构体,也就是pcb;在pcb中,有一个指针*files,指向一张表file_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针。本质上,文件描述符就是该数组的下标,所以,只要拿到文件描述符,就能找到该文件。
fd只是一个整数,在open时产生。起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针*file,该指针指向一个结构体FILE,这个结构体中包含着我们所打开文件的属性信息。
文件描述符的分配规则:
文件描述符从0开始,0,1,2分别默认代表标准输入,标准输出,标准错误,这三者是默认打开的。所以平时从3开始分配,把最小的 ,为被使用下标(fd)的分配出去。
方法:从0号下标开始遍历,只要数组指向是无效的,就把新打开的文件地址填充至对应的数组下标。
file结构体的几个重要的成员变量:
f_flags:表示打开文件的权限 。
f_pos:表示当前读写文件的位置。
f_count:表示打开文件的引用计数,如果有多个文件指针指向它,就会增加f_count的值。
f_mode:设置对文件的访问模式,例如:只读,只写、可读可写等。
file_struct是操作系统用来管理文件的数据结构,;
当我们创建一个进程时,会创建文件描述符表files_struct;
进程控制块PCB中的*files指针指向文件描述符表;
当我们创建文件时,会为指向该文件的指针file*关联一个文件描述符并添加在文件描述符表中;
在文件描述符表中fd相当于数组的索引,file*相当于数组的内容,指向一个文件结构体。
升级shell,使其支持输入/输出/追加重定向
在前面的博客已经编写了一个初级的shell,有兴趣的朋友可以看下:
https://blog.csdn.net/qq_41209741/article/details/83089000
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<string.h>
#include<ctype.h>
#include<fcntl.h>
int main ()
{
for(;;)
{
printf("myshell@bogon:");
fflush(stdout);
char buf[1024];
int read_size = read(1,buf,sizeof(buf)-1);
if(read_size > 0){
buf[read_size-1] = 0;
}
char *shell_argv[32] = {NULL};
int shell_index = 0;
char *start = buf;
while(*start != '\0'){
while(*start != '\0' && isspace(*start)){
*start = '\0';
start++;
}
shell_argv[shell_index++] = start;
while(*start != '\0' && !isspace(*start)){
start++;
}
}
//创建子进程的exec
pid_t pid = vfork();
if(pid < 0){
printf("vfork failure");
exit(1);
}
else if(pid == 0){
//子进程重定向
int i = 0;
int flag = 0;
for(;shell_argv[i]!=NULL;i++){
if(strcmp(">",shell_argv[i]) == 0){
flag = 1;
break;
}
}
int copyfd;
shell_argv[i] = NULL;
if(flag)
{
if(shell_argv[i+1]==NULL){
printf("command error\n");
exit(1);
}
close(1);
int fd = open(shell_argv[i+1],O_WRONLY,0777);
copyfd = dup2(1,fd);//把标准输出重定向到下一个文件中
}
execvp(shell_argv[0],shell_argv);
if(flag){
close(1);
dup2(copyfd,1);
}
exit(1);
}
else// father process
{
int status = 0;
int ret = waitpid(pid,&status,0);
if(ret == pid){
if(WIFEXITED(status)){
}else if(WIFSIGNALED(status)){
printf("signal is %d\n",WTERMSIG(status));
}
}
}
}
return 0;
}
这次升级过后的shell无疑比之前的版本更加强大,但是还有很多部分没有完善的地方,欢迎指正。
编写简单的add/sub/mul/div函数,并打包成动/静态库,并分别使用。
静态库:程序在编译链接的时候把库的代码链接到可执行文件中。程序运行的时候将不再需要静态库
动态库:程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码。
一个与动态库链接的可执行文件仅仅包含用到的函数入口地址的一个表,而不是外部函数所在目标文件的整个机器码。
在可执行文件开始运行以前,外部函数的机器码由擦欧洲哦系统从磁盘上的该动态库中复制到内存中,这个过程称为动态链接。
动态库可以在逗哥程序建共享,五哦一动态链接是的可执行文件更小,节省了磁盘空间。操作系统采用虚拟内存机制允许物理内存中的一份动态库被要用到该哭的所有进程共用,节省了内存和磁盘空间。
打包静态库,并且使用它
第一步:写好add/sub/mul/div函数的.h和.c文件
//add.h
#ifndef __ADD_H__
#define __ADD_H__
#include<stdio.h>
int add(int x, int y);
#endif
//add.c
#include"add.h"
int add(int x, int y)
{
return x+y;
}
//sub.h
#ifndef __SUB_H__
#define __SUB_H__
#include<stdio.h>
int sub(int x, int y);
#endif
//sub.c
#include"sub.h"
int sub(int x, int y)
{
return x-y;
}
/
//mul.h
#ifndef __MUL_H__
#define __MUL_H__
#include<stdio.h>
int mul(int x, int y);
#endif
//mul.c
#include"mul.h"
int mul(int x, int y)
{
return x*y;
}
//div.h
#ifndef __DIV_H__
#define __DIV_H__
#include<stdio.h>
int div(int x, int y);
#endif
//div.c
#include"div.h"
int div(int x, int y)
{
return x/y;
}
第二步:由.c文件生成.o文件
gcc -c add.c sub.c mul.c div.cclear
第三步:由.o文件生成.a文件,也就是生成静态库文件
ar -rc libmymath.a add.o sub.o mul.o div.o
第四步:编写test.c文件
#include"add.h"
#include"sub.h"
#include"mul.h"
#include"div.h"
int main ()
{
printf("10+2=%d\n",add(10,2));
printf("10-2=%d\n",sub(10,2));
printf("10*2=%d\n",mul(10,2));
printf("10/2=%d\n",div(10,2));
return 0;
}
第五步:编译:
gcc test.c -L. -lmymath
第六步:执行:
./a.out
第七步:我们还可以把头文件跟静态库打包到一个文件中
第九步:重新编译:
rm -f a.out
gcc test.c -I./mylib/include/ -L./mylib/lib -lmymath
./a.out编译选项:
-L 指定库路径,从左到右搜索-L指定的目录。-I(大写的i)指定库名
-l(小写的L)引用的库名,省略掉lib 和 .a或者.so
库命名规则:libxxx.a
此时我们就完成了我们的打包静态库了,库里面包含4个函数。
打包动态库,并且使用它
第一步:沿用之前的.c和.h文件。只是生成的.o文件有所差异
gcc -fPIC -c add.c sub.c mul.c div.c
gcc -shared -o libmymath.so *.o
编译选项:
shared:表示生成共享库格式
fPIC:产生位置无关码
库命名规则:libxxx.so
第二步:我们同样可以把libmymath.so拷贝到./mylib/lib中。
cp libmymath.so mylib/lib
第三步:编译:
gcc test.c -I./mylib/include -L./mylib/lib -lmymath
第四步:修改ID_LIBRARY_PATH
ID_LIBRARY_PATH是由环境变量指定的目录
export ID_LIBRARY_PATH=./mylib/lib/
第五步:执行:
./a.out
总结:
静态库(.a):程序在编译链接的时候把库的代码链接(拷贝)到可执行文件中。程序运行的时候将不再需要静态库。
静态库缺点:浪费空间,内存,硬盘
优点:可移植性好
打包静态库,需要提供.a .h
ldd a.out 查看链接关系
动态库(.so):程序在运行的时候才去链接动态库的代码,多个程序共享使用库的代码
优点:节省空间
动态库默认在一个环境变量里寻找
动态库运行需要填写默认关系:export LD_LIBRARY_PATH=.
动态库跟静态库在准备链接的时候才引入库 此时是.o文件,是二进制文件。所以静态库和动态库都是二进制文件
加载:按需加载 有个函数入口地址的表。
动态库在编译之后也一直依赖,而静态库不需要依赖。
动态库首先是在磁盘上放着,当需要使用动态库时,就把动态库加载到内存上,再由虚拟地址通过页表映射,
C库即有动态又有静态。通过环境变量找到