程序员成长之旅——基础IO

open/read/write/close等文件相关系统调用接口

open

在这里插入图片描述
注释解析:
(1)函数声明:用来打开一个已经存在的文件或者创建一个普通文件
(2)参数解释
pathname:要打开或者创建的目标文件
flags:打开文件的时候,可以传入多个参数选项,用下面的一个或多个进行“或”运算,构成flags;
参数O_RDONLY----只读打开、
O_WRONLY----只写打开、
O_RDWR----读、写打开;
这三个变量只能指定一个
O_CREAT----若文件不存在,则创建它。需要使用mode(文件权限标志)选项,来指明权限
O_APPEND----追加写,要配合上面三个变量中的一个
(3)返回值:成功返回新打开文件的描述符,失败则返回-1

read
//函数原型
ssize_t read(int fd,void *buf,size_t count);

注释:
(1)函数声明:是从fd所描述的打开文件中读取buf所缓冲区的n个字节
(2)参数说明
fd:文件描述符,用来指向要操作的文件的文件结构体
buf:一块内存空间
count:请求读取的字节数,读上来的数缓冲区buf中,同时文件的当前读写位置向后移。
ssize_t:有符号整型
(3)返回值:成功返回读取的字节数,出错返回-1并设置errno,如果在调用read之前已到达文件末尾,则这次read返回0。
注意:读操作从文件的当前位移量处开始,在成功返回之前,该位移量增加实际读的字数。

write
//函数原型
ssize_t write(int fd,const void *buf,size_t count);

注释:
(1)函数声明:将 buf所指向的缓冲区的 n字节写入 fd 所描述的打开文件中
(2)参数说明
fd:文件指针
buf:写入的数据保存在缓冲区buf中,同时文件的当前读写位置向后移
count:请求写入的字节数
(3)返回值:成功返回写入的字节数,出错返回-1并设置errno写常规文件时,write的返回值通常等于请求写的字节数count,而向终端或者网络写则不一定。

close

在这里插入图片描述
注释:
(1)函数声明:关闭指定文件
(2)参数说明:fd—文件描述符,用来指向要操作的文件的文件结构体
(3)返回值:若成功返回0,出错返回-1;
注意:关闭一个文件时也释放该进程加在该文件上的所有记录锁,当一个进程终止时,它所有的打开文件都由内核自动关闭。

纵向对比fd与FILE结构体

文件描述符fd

我们都知道在Linux下一切皆文件。当然设备也不例外,如果要对某个设备进行操作,就不得不打开此设备文件,打开文件就会获得该文件的文件描述符fd( file discriptor), 它就是一个很小的整数,每个进程在PCB中都有一个指针*files,指向一张表files_struct,表中包含一个指针数组,每个元素都是指向打开文件的指针。文件描述符就是这个表的索引,即为该数组的下标。

在这里插入图片描述
文件描述符分配规则:在 file_struct数组中,找到当前没有被使用的最小下标,作为一个新的文件描述符。
注意:一般情况下,file_struct数组下标的 0,1,2 分别被标准输入(键盘),标准输出(显示器),标准错误(显示器)占用,只有当调用close()函数,关闭掉某个下标 ,fd才会是0或1,或2.若没有被关闭的,fd会是3。 其次,由于数组下标没有负数,所以fd也不会是负数。
所以,通过上面的介绍,可以知道,fd的本质其实就是一个表示数组下标的小整数。

FILE结构体
struct FILE
{
    char *_ptr;//文件输入的下一个位置
    int _cnt;//当前缓冲区的相对位置
    char *_base;//指基础位置(文件的起始位置)
    int _flag;//文件标志
    int _file;//文件的有效性验证
    int _charbuf;//检查缓冲区状况,如果缓冲区则不读取
    int _bufsiz;//文件的大小
    char *_tmpfname;//临时文件名
};
FILE*文件指针

文件指针指向进程用户区中一个被叫做FILE结构的结构数据。FILE结构包括一个缓冲区和一个文件描述符 。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄(在Windows系统上,文件描述符被称作文件句柄)。
通常,任何程序运行起来之后都会默认的打开三个标准输入流(stdin:键盘),标准输出流(stdout:显示器),标准错误流(stderr:显示器)。

简单来说,fd是一个一个表示数组下标的小整数。FILE为一个结构体,FILE* 为文件指针,指向文件。

文件描述符和文件指针的区别

fd是一个表示数组下标的整数,在打开文件的过程中,起到一个索引的作用,进程通过PCB中的文件描述符找到所指向的文件指针file*。
open:文件描述符的操作(如:open)返回的是一个文件描述符(int fd),内核会在每个进程空间中维护一个文件描述符表,此表中的文件描述符来引用。
fopen:流(如:fopen)返回的是一个文件指针(即指向FILE结构体的指针),FILE结构是包含有文件描述符的,fopen可以看做是open(fd直接操作的系统调用)的封装,它的优点是带有I/O缓存。

之前编写的自主shell进行修改,使其支持输入/输出/追加重定向

#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

//建立一个子进程(fork)
//在子进程中获取命令行
//在子进程中解析命令行,并判断是那种重定向替换相应的文件描述符
//替换子进程(execvp)
//父进程等待子进程退出(wait)


int main()
{
  char buf[1024] = {0};
  while(1){
    //打印提示符
    printf("[lpf@MyShell]$");
    fflush(stdout);
    //获取命令行
    fgets(buf,sizeof(buf) - 1,stdin);
    buf[strlen(buf) - 1] = '\0';
    //解析命令
    char *argv[8] = {NULL};
    argv[0] = strtok(buf," ");
    int argc = 0;
    while(argv[argc]){
      argv[++argc] = strtok(NULL," ");
    }
    //创建子进程
    pid_t id = fork();
    if(id < 0){
      perror("fork");
    }else if(id == 0){
      //子进程
      int i = 0;
      for(i = 0;i < argc;++i){
        if(strcmp(argv[i],">") == 0){
          int fd = open(argv[i+1],O_WRONLY|O_CREAT);
          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;
        }else if(strcmp(argv[i],"<") == 0){
          int fd = open(argv[i+1],O_RDONLY,0644);
          dup2(fd,1);
          argv[i] = NULL;
        }
      }
      //子进程程序替换
      execvp(argv[0],argv);
      perror("execvp");
    }else{
      //父进程等待子进程
      wait(NULL);
    }
  }
  return 0;
}

效果演示
1.普通的
在这里插入图片描述
2.>>
在这里插入图片描述
3.>
在这里插入图片描述
4.<
在这里插入图片描述

编写简单的add/sub/mul/div函数,并打包成动/静态库,并分别使用

静态库

.h头文件

#ifdef _ADD_H_
#define _ADD_H_
#include<stdio.h>

int myadd(int a,int b);

#endif
#ifdef _DIV_H_
#define _DIV_H_
#include<stdio.h>

int mydiv(int a,int b);

#endif
#ifdef _MUL_H_
#define _MUL_H_
#include<stdio.h>

int mymul(int a,int b);

#endif
#ifdef _SUB_H_
#define _SUB_H_
#include<stdio.h>

int mysub(int a,int b);

#endif

.c源文件

#include"add.h"

int myadd(int a,int b)
{
  return a + b;
}
#include"div.h"

int mydiv(int a,int b)
{
  return a / b;
}
#include"mul.h"

int mymul(int a,int b)
{
  return a * b;
}
#include"sub.h"

int mysub(int a,int b)
{
  return a - b;
}

静态库的生成Makefile
在这里插入图片描述
这是将上面的.h和.c源文件打包生成了静态库lib
然后编写cal.c并在Makefile输入下面这个链接找到库文件就可以运行了
在这里插入图片描述
-I后面找的是.h文件
-L找的是静态库所在的目录
-l找的是库名
在这里插入图片描述
静态库名是去掉后缀.a和lib这个剩下的才是库名
ldd命令可以查看可执行程序依赖的第三方库
在这里插入图片描述
我们发现,并没有依赖静态库,所以我们知道静态库是在链接的时候向可执行程序中拷贝一份,所以我们并不依赖静态库。

#include<stdio.h>
#include"add.h"
#include"sub.h"
#include"mul.h"
#include"div.h"

int main()
{
  printf("PLease enter: ");
  int x,y,z;
  scanf("%d %d",&x,&y);
  z = myadd(x,y);
  printf("%d\n",z);
  z = mysub(x,y);
  printf("%d\n",z);
  z = mymul(x,y);
  printf("%d\n",z);
  z = mydiv(x,y);
  printf("%d\n",z);
  return 0;
}

上面的运行结果是:
在这里插入图片描述

动态库

shared----表示生成共享库格式
FPIC----产生位置无关码
库名规则----libxxx.so
打包成动态库先需要更改一下Makefile如下图所示
在这里插入图片描述
然后我们make一下,在ldd一下,我们会发现要依赖的库知道了,但是程序找不到库
在这里插入图片描述
这时候我们只需要加入这句命令即可
ldd cal发现可以找到了
在这里插入图片描述
运行也是可以完成
在这里插入图片描述
综上所述,我们可以总结一下动静态库的区别也就是优缺点:

静态库

  • 代码装载速度快,执行速度略比动态链接库快;
  • 只需保证在开发者的计算机中有正确的.LIB文件,在以二进制形式发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题,可避免DLL地域等问题。
  • 使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费;

动态库

  • 更加节省内存并减少页面交换;
  • DLL文件与EXE文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性;
  • 不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数;
  • 适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和测试。
  • 使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统撕掉。这在早期Windows中很常见。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从零出发——

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值