Linux中的基础IO(二)

Linux中的基础IO(二)

一、基本接口

int open(const char *pathname, int flags, mode_t mode)

const char *pathname:代表要打开或要创建的目标文件。

int flags:可传入多个参数选项,进行或运算组成flags

选项 解释
O_RDONLY 以只读的方式打开文件
O_WRONLY 以只写的方式打开文件
O_RDWR 读,写打开,(打开方式必须三选一)
O_CREAT 若文件不存在,则创建文件,需要使用mode选项指明新创建文件的权限(mode用函数umask()进行设置当前进程创建的文件的权限掩码,如umask(0)等)
O_APPEND 追加写
O_TRUNE 打开文件时,清空原有文件中的内容

如:写文件

#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", O_WRONLY|O_CREAT, 0644);
if(fd < 0){
perror("open");
return 1;
}
int count = 5;
const char *msg = "hello bit!\n";
int len = strlen(msg);
while(count--){
write(fd, msg, len);//fd: 文件描述符, msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数
据。 返回值:实际写了多少字节数据
}
close(fd);
return 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", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
const char *msg = "hello bit!\n";
char buf[1024];
while(1){
ssize_t s = read(fd, buf, strlen(msg));//类比write
if(s > 0){
printf("%s", buf);
}else{
break;
}
}
close(fd);
return 0;
}

二、文件描述符

1.文件描述符和文件流指针的区别:

文件流指针:文件流指针是标准库函数操作的句柄,类型为FILE*结构体类型。

文件描述符:文件描述符是系统调用的句柄,类型是int型,注意⚠️,文件流指针中包含文件描述符

2.Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0,标准输出1,标准错误2,所以输入输出可以采用下面 的方式

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{
char buf[1024];
ssize_t s = read(0, buf, sizeof(buf));
if(s > 0){
buf[s] = 0;
write(1, buf, strlen(buf));
write(2, buf, strlen(buf));
}
return 0;
}

三、文件描述符的分配规则:

1.文件描述符的管理
在这里插入图片描述
结合上图:我们知道文件描述符都是从0开始的整数,当我们打开一个文件时,操作系统要在内存中创建相应的数据结构来管理描述目标文件。于是就有了file结构体,表示已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。描述进程的是进程控制块PCB,Linux中是task_struct,内部除了描述进程相关信息外,还有一个指针*file,指向一张表file_struct,这张表最重要的部分是包含一个指针数组,数组中的每个元素都指向一个打开文件的指针,所以,本质上文件描述符就是该数组的下标,即只要拿到数组下标,取数组中的file指针就可以找到对应的文件,上面就是操作系统管理文件描述符的方式。

2.文件描述符的分配规则:
看下面的代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}

在这里插入图片描述
发现文件描述符fd为3。那么关闭0或者2再试试:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}

在这里插入图片描述
发现结果是0或者2,由此得出文件描述符的分配规则是:在file_struct结构体中,找当前没有被使用的最小的下标,作为新的文件描述符

四、重定向
1.重定向:

先看代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
close(1);
int fd = open("myfile", O_WRONLY|O_CREAT, 00644);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
fflush(stdout);
close(fd);
exit(0);
}

在这里插入图片描述
此时,我们发现,本来应该输出到显示器上的内容,输出到了文件 myfile 当中,因为一开始我们关闭了标准输出文件描述符1,导致按照文件描述符的分配规则,fd就得到最小未使用的文件描述符,其代表数组的元素内容是指向myfile文件的,所以就输出到myfile中,其中,fd=1。这种现象叫做输出重定向。常见的重定向有:>(清空重定向), >>(追加重定向), <(标准输入重定向)。

2.重定向的原理:

在这里插入图片描述
结合上图,重定向的本质是重新定向文件描述符所在的file指针的指向,即改变以个文件描述符所对应的文件信息,进而改变操作的文件,注意⚠️文件描述符重定向的过程中文件描述符的数量没有发生改变,只是改变其数组内部file指针的指向而已。

五、dup2系统调用

int dup2(int oldfd, int newfd);

作用是将newfd文件描述符的file指针指向oldfile文件描述符的file指针指向的文件。

看代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
int fd = open("myfile", O_CREAT | O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
close(1);
dup2(fd, 1);
for (;;) {
char buf[1024] = {0};
ssize_t read_size = read(0, buf, sizeof(buf) - 1);
if (read_size < 0) {
perror("read");
break;
}
printf("%s", buf);
fflush(stdout);
}
return 0;
}

在这里插入图片描述
说明使用dup2也可以改变文件描述符的指向(重定向)

六、minishell

#include <stdio.h>
#include <sys/wait.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

char g_command[1024];

//该函数的功能是获取命令行输入的数据
int GetCommand()
{
  //因为开始获得的内存中的内容是不定的,所以使用前要先初始化
  memset(g_command,'\0',sizeof(g_command));
  //这里需要为'\0'留一个位置,不能把g_command全部用来读取内容,否者就没有结束标志,容易发生内存访问越界
  if(fgets(g_command,sizeof(g_command)-1,stdin)==NULL)
  {
    printf("fgets is error!\n");
    return -1;
  }
  //printf("g_command:%s\n",g_command);
  return 0;
}

//解析字符串
char** DealCommand(char* command)
{
  if(command==NULL&&*command=='\0')
  {
    printf("dealcommand is error\n");
    return NULL;
  }

  //用来保存命令
  static char* argv[1024]={0};
  int argc=0;
  while(*command)
  {
    //isspace函数用来去掉多余的空格
    //isspace的返回值:如果当前字符是空格返回1,否则返回0
    //注意'\0'在issapce函数中不算空格的,所以要进行判断
    while(!isspace(*command)&&*command!='\0')
    {
      argv[argc]=command;
      argc++;

      //去找下一个空格
      while(!isspace(*command)&&*command!='\0')
      {
        command++;
      }
      *command='\0';
    }
    command++;
  }
  argv[argc]=NULL;

  //for(int i=0;i<argc;i++)
  //{
  //  printf("%d:%s   ",i,argv[i]);
  //}
  //printf("\n");

  return argv; 
}

//进行重定向
int redirect(char * g_command)
{
  char* ptr = g_command;
  char* file = NULL;
  int fd ;
  //用来标记是清空重定向还是追加重定向
  int redirect_type = -1;

  while(*ptr !='\0')
  {
    //如果当前字符是 > ,把他置为'\0',并判断下一个位置是否为'\0'
    if(*ptr == '>')
    {
      *ptr++ = '\0';
      redirect_type++;
      if(*ptr == '>')
      {
        *ptr++ = '\0';
        redirect_type++;
      }
      //去掉多余的空格
      while(isspace(*ptr))
      {
        ptr++;
      }
      //file就是空格后面的第一个字符串
      file = ptr;

      //继续找空格,在这两个空格之间就是文件的名称
      while(!isspace(*ptr)&&*ptr != '\0')
      {
        ptr++;
      }

      *ptr='\0';
      //如果redirect_type==0说明是清空重定向,如果==1说明是追加重新定向
      if(redirect_type == 0)
      {
        fd = open(file,O_CREAT|O_TRUNC|O_WRONLY,0664);
      }
      else
      {
        fd = open(file,O_CREAT|O_APPEND|O_WRONLY,0664);
      }
      dup2(fd,1);
    }
    ptr++;
  }
  return 0;
}

//进行程序替换
int exec()
{
  redirect(g_command);
  char** argv=DealCommand(g_command);
  pid_t pid =fork();
  if(pid<0)
  {
    printf("foek is error!\n");
    return -1;
  }
  else if(pid==0)
  {
    //child
    //如果argc中为NULL,就直接返回
    if(argv[0]==NULL)
    {
      exit(-1);
    }
    //进行替换,execvp第一个参数是可执行程序名,第二个参数是该可执行程序的参数组成的数组
    execvp(argv[0],argv);
    //execl("/usr/bin/ls","ls","-a",NULL);
  }
  else
  {
    //father
    waitpid(pid,NULL,0);
  }
  return 0;
}



int main()
{
  //循环读数据
  while(1)
  {
    printf("[dev@localhost dev]$ ");
    int ret = GetCommand();
    if(ret == -1)
    {
      //如果读取失败,继续循环读取,不能直接break调
      continue;
    }

    //处理解析数据
    //char** argv = DealCommand(g_command);

    //进行替换
    //exec(argv);
    exec();
  }

  return 0;
}

发布了49 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/wolfGuiDao/article/details/103860864