Unix环境高级编程-学习-01-输入和输出

目录

一、环境信息

二、声明

三、名词解释

1、文件描述符

2、标准输入、标准输出和标准错误

四、实验

1、MyCpNoBuf.c

(1)C源码

(2)函数介绍

2、MyCpBuf.c

(1)C源码

(2)函数介绍

3、MyCpFgetc.c

(1)C源码

(2)函数介绍

4、MyCpFgets.c

(1)C源码

(2)函数介绍

5、makefile

6、编译

7、测试文件

8、对比测试

(1)操作系统cp

(2)MyCpNoBuf

(3)MyCpBuf

(4)MyCpFgetc

(5)MyCpFgets

9、总结


一、环境信息

|------|-------------------------------------------|
| 名称 | 值 |
| CPU | Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz |
| 操作系统 | CentOS Linux release 7.9.2009 (Core) |
| 内存 | 3G |
| 逻辑核数 | 2 |

二、声明

本文部分内容参考了《Unix环境高级编程》第三版,这本书写的很好,推荐大家进行阅读。

三、名词解释

1、文件描述符

文件描述符通常是一个小的非负整数,内核用以标识一个特定进程正在访问的文件。当内核打开一个现有文件或创建一个新文件时,它都会返回一个文件描述符。在读、写文件时,可以使用这个文件描述符。

2、标准输入、标准输出和标准错误

每当运行一个新的程序时,所有shell都为其打开3个文件描述符,即标准输入、标准输出、标准错误。如果不做特殊处理,这个三个描述符都会链接向终端。

四、实验

我们来通过几种C标准函数、系统调用来实现一下复制文件这个功能,来看一下效率上有什么差别。

1、MyCpNoBuf.c

(1)C源码

cpp 复制代码
#include <stdio.h>
#include <sys/io.h>
#include <unistd.h>
#include <stdlib.h>

#define ONE_PAGE_MEM_SIZE 4096
#define EXCEPTION_STATUS  -1

int main()
{
    int  ReadBytes = 0;
    char Buf[ONE_PAGE_MEM_SIZE];

    while ((ReadBytes = read(STDIN_FILENO,Buf,ONE_PAGE_MEM_SIZE)) > 0)
    {
        if (ReadBytes != write(STDOUT_FILENO,Buf,ReadBytes))
        {
            perror("write Error");
            exit(EXCEPTION_STATUS);
        } 
    }
    
    if (ReadBytes < 0)
    {
        perror("read Error");
    }
    
    return 1;
}

(2)函数介绍

|-----|--------------------------------------------------------------|-------------------------------------------------------------|
| 函数名 | read | write |
| 头文件 | #include <sys/io.h> | #include <sys/io.h> |
| 声明 | ssize_t read(int __fd, void *__buf, size_t __nbytes) | ssize_t write(int __fd, const void *__buf, size_t __n) |
| 参数 | 1、__fd:读取文件的文件句柄。 2、__buf:将读取内容保存的缓冲区。 3、__nbytes:读取内容的字节数。 | 1、__fd:写入文件的文件句柄。 2、__buf:写入内容的缓冲区。 3、__n:写入内容的字节数。 |
| 返回值 | 返回实际读取的字节数。 | 返回写入的数字,或 -1。 |
| 描述 | 读取文件。属于系统调用。 1、返回值大于0,读取成功。 2、返回值小于0,读取失败。 3、返回值等于0,没有读取到数据。 | 写入文件。属于系统调用。 1、返回值大于0,写入成功。 2、返回值小于0,写入失败。 3、返回值等于0,没有写入数据。 |

2、MyCpBuf.c

(1)C源码

cpp 复制代码
#include <stdio.h>
#include <sys/io.h>
#include <unistd.h>
#include <stdlib.h>

#define EXCEPTION_STATUS  -1

int main()
{
    int Chr;

    while ((Chr = getc(stdin)) != EOF)
    {
        if (putc(Chr,stdout) == EOF)
        {
            perror("putc Error");
            exit(EXCEPTION_STATUS);
        } 
    }
    
    if (ferror(stdin))
    {
        perror("getc Error");
    }
    
    return 1;
}

(2)函数介绍

|-----|----------------------------------|-------------------------------------------|
| 函数名 | getc | putc |
| 头文件 | #include <stdio.h> | #include <stdio.h> |
| 声明 | #define getc(_fp) _IO_getc (_fp) | #define putc(_ch,_fp) _IO_putc (_ch, _fp) |
| 参数 | 1、_fp:读取文件的文件句柄。 | 1、_ch:写入的字符。 2、_fp:读取文件的文件句柄。 |
| 返回值 | 成功返回字符,错误或读取到文件末尾返回EOF。 | 成功返回字符,写入失败返回EOF。 |
| 描述 | 读取一个字符。 | 写一个字符。 |

3、MyCpFgetc.c

(1)C源码

cpp 复制代码
#include <stdio.h>
#include <sys/io.h>
#include <unistd.h>
#include <stdlib.h>

#define EXCEPTION_STATUS  -1

int main()
{
    int Chr;

    while ((Chr = fgetc(stdin)) != EOF)
    {
        if (fputc(Chr,stdout) == EOF)
        {
            perror("fputc Error");
            exit(EXCEPTION_STATUS);
        } 
    }
    
    if (ferror(stdin))
    {
        perror("fgetc Error");
    }
    
    return 1;
}

(2)函数介绍

|-----|----------------------------|-------------------------------------|
| 函数名 | fgetc | fputc |
| 头文件 | #include <stdio.h> | #include <stdio.h> |
| 声明 | int fgetc(FILE *__stream) | int fputc(int __c, FILE *__stream) |
| 参数 | 1、__stream:读取文件的流。 | 1、__c:写入的字符。 2、__stream:写入文件的流。 |
| 返回值 | 成功返回字符,错误或读取到文件末尾返回EOF。 | 成功返回字符,写入失败返回EOF。 |
| 描述 | 从流中读取一个字符。 | 往流中写一个字符。 |

4、MyCpFgets.c

(1)C源码

cpp 复制代码
#include <stdio.h>
#include <sys/io.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#define EXCEPTION_STATUS  -1
#define ONE_PAGE_MEM_SIZE 4096

int main()
{
    char ReadStr[ONE_PAGE_MEM_SIZE];

    while (fgets(ReadStr,ONE_PAGE_MEM_SIZE,stdin) != NULL)
    {
        if (fputs(ReadStr,stdout) == EOF)
        {
            perror("fputs Error");
            exit(EXCEPTION_STATUS);
        } 
    }
    
    if (ferror(stdin))
    {
        perror("fgets Error");
    }
    
    return 1;
}

(2)函数介绍

|-----|--------------------------------------------------------------------------------------------|------------------------------------------------------------------------|
| 函数名 | fgets | fputs |
| 头文件 | #include <stdio.h> | #include <stdio.h> |
| 声明 | char *fgets(char *restrict __s, int __n, FILE *restrict __stream) | int fputs(const char *restrict __s, FILE *restrict __stream) |
| 参数 | 1、__s:读取到字符串。 2、__n:读n-1个字符。 3、__stream:读取文件的流。 | 1、__s:写入的字符串。 2、__stream:写入文件的流。 |
| 返回值 | 成功返回字符串,错误或读取到文件末尾返回NULL。 | 成功返回最后的字符,写入失败返回EOF。 |
| 描述 | 从流中读取一个字符串。从流中读n-1个字符,或当遇换行符'\n'、'\0'为止,把读出的内容,存入__s中。与gets不同,fgets在s未尾保留换行符。会自动补充'\0'。 | 往流中写一个字符。 |

5、makefile

bash 复制代码
CC                 = gcc
STD_VERSION        = -std=gnu11
OPTIMIZATION_LEVEL = -O3 $(STD_VERSION) 
CFLAG_O            = -c -Wall -Wextra -fpic ${OPTIMIZATION_LEVEL}
CFLAG_SO           = -shared -Wall -Wextra ${OPTIMIZATION_LEVEL}
CFLAG_NEW_SO       = -shared -Wall -Wextra -fpic ${OPTIMIZATION_LEVEL}
CFLAG_EXEC         = -Wall -Wextra ${OPTIMIZATION_LEVEL}
CFLAG_ALIAS        = -o
INCLUDE_COMMAND    = -I
LIB_COMMAND        = -L
LIB_NAME           = -l
RM_COMM            = rm -rf

UNIX_CODE_PATH     = /opt/Developer/ComputerLanguageStudy/C/Unix/
UNIX_SRC_PATH      = ${UNIX_CODE_PATH}Src/
UNIX_EXEC_PATH     = ${UNIX_CODE_PATH}Exec/


all : MyCpNoBuf

MyCpNoBuf : MyCpBuf
	$(CC) $(CFLAG_EXEC) MyCpNoBuf.c $(CFLAG_ALIAS) ${UNIX_EXEC_PATH}MyCpNoBuf

MyCpBuf : MyCpFgetc
	$(CC) $(CFLAG_EXEC) MyCpBuf.c $(CFLAG_ALIAS) ${UNIX_EXEC_PATH}MyCpBuf

MyCpFgetc : MyCpFgets
	$(CC) $(CFLAG_EXEC) MyCpFgetc.c $(CFLAG_ALIAS) ${UNIX_EXEC_PATH}MyCpFgetc

MyCpFgets : 
	$(CC) $(CFLAG_EXEC) MyCpFgets.c $(CFLAG_ALIAS) ${UNIX_EXEC_PATH}MyCpFgets

clean : 
	$(RM_COMM) ${UNIX_EXEC_PATH}*

6、编译

大家根据自己的目录结构进行调整。

bash 复制代码
[gbase@czg2 Src]$ ll
总用量 20
-rw-r--r-- 1 root  root  1069 10月 27 14:36 makefile
-rw-rw-r-- 1 gbase gbase  406 10月 27 10:46 MyCpBuf.c
-rw-r--r-- 1 root  root   410 10月 30 10:18 MyCpFgetc.c
-rw-r--r-- 1 root  root   508 10月 30 10:18 MyCpFgets.c
-rw-rw-r-- 1 gbase gbase  538 10月 27 18:10 MyCpNoBuf.c

[gbase@czg2 Src]$ make
gcc -Wall -Wextra -O3 -std=gnu11  MyCpFgets.c -o /opt/Developer/ComputerLanguageStudy/C/Unix/Exec/MyCpFgets
gcc -Wall -Wextra -O3 -std=gnu11  MyCpFgetc.c -o /opt/Developer/ComputerLanguageStudy/C/Unix/Exec/MyCpFgetc
gcc -Wall -Wextra -O3 -std=gnu11  MyCpBuf.c -o /opt/Developer/ComputerLanguageStudy/C/Unix/Exec/MyCpBuf
gcc -Wall -Wextra -O3 -std=gnu11  MyCpNoBuf.c -o /opt/Developer/ComputerLanguageStudy/C/Unix/Exec/MyCpNoBuf

7、测试文件

大家可以找个大文本,重复写大一些,测试的效果明显一些,千万不要用dd if=/dev/zero of=../Exec/Data bs=1M count=1000来生成,因为/dev/zero中存的是空字符也就是'0',fgtes方法会读取一个开头字符就停止,达不到效果。我们可以用命令od看一下,-c表示显示以字符方式打印内容。

bash 复制代码
[root@czg2 gbase]# dd if=/dev/zero bs=1024 count=1 of=Data
记录了1+0 的读入
记录了1+0 的写出
1024字节(1.0 kB)已复制,0.000380848 秒,2.7 MB/秒

[root@czg2 gbase]# od -c Data 
0000000  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0  \0
*
0002000

下面这个是我们的测试文件。

bash 复制代码
[gbase@czg2 Src]$ du -sh /home/gbase/TestData.sql 
1.2G    /home/gbase/TestData.sql

8、对比测试

每一组我们都测试三次,选取最快的结果。

(1)操作系统cp

bash 复制代码
[gbase@czg2 Src]$ time cp /home/gbase/TestData.sql /home/gbase/TestDataBak.sql 

real    0m4.791s
user    0m0.005s
sys     0m2.141s
[gbase@czg2 Src]$ du -sh /home/gbase/TestData*
1.2G    /home/gbase/TestDataBak.sql
1.2G    /home/gbase/TestData.sql

(2)MyCpNoBuf

bash 复制代码
[gbase@czg2 Src]$ time ../Exec/MyCpNoBuf < /home/gbase/TestData.sql > /home/gbase/TestDataBak.sql 

real    0m5.056s
user    0m0.026s
sys     0m2.207s
[gbase@czg2 Src]$ du -sh /home/gbase/TestData*
1.2G    /home/gbase/TestDataBak.sql
1.2G    /home/gbase/TestData.sql

(3)MyCpBuf

bash 复制代码
[gbase@czg2 Src]$ time ../Exec/MyCpBuf < /home/gbase/TestData.sql > /home/gbase/TestDataBak.sql 

real    0m16.911s
user    0m13.576s
sys     0m3.266s
[gbase@czg2 Src]$ du -sh /home/gbase/TestData*
1.2G    /home/gbase/TestDataBak.sql
1.2G    /home/gbase/TestData.sql

(4)MyCpFgetc

bash 复制代码
[gbase@czg2 Src]$ time ../Exec/MyCpFgetc < /home/gbase/TestData.sql > /home/gbase/TestDataBak.sql 

real    0m14.686s
user    0m12.103s
sys     0m2.549s
[gbase@czg2 Src]$ du -sh /home/gbase/TestData*
1.2G    /home/gbase/TestDataBak.sql
1.2G    /home/gbase/TestData.sql

(5)MyCpFgets

bash 复制代码
[gbase@czg2 Src]$ time ../Exec/MyCpFgets < /home/gbase/TestData.sql > /home/gbase/TestDataBak.sql 

real    0m4.779s
user    0m1.197s
sys     0m2.147s
[gbase@czg2 Src]$ du -sh /home/gbase/TestData*
1.2G    /home/gbase/TestDataBak.sql
1.2G    /home/gbase/TestData.sql

9、总结

从测试结果来看read、write、fgets、fputs效率上较高,但read、write属于系统调用,并不适用于平台移植,fgets、fputs属于C标准库,方便移植推荐。

相关推荐
Narutolxy3 分钟前
深入探讨 Go 中的高级表单验证与翻译:Gin 与 Validator 的实践之道20241223
开发语言·golang·gin
bitcsljl5 分钟前
Linux 命令行快捷键
linux·运维·服务器
Hello.Reader10 分钟前
全面解析 Golang Gin 框架
开发语言·golang·gin
禁默21 分钟前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Code哈哈笑30 分钟前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
程序猿进阶34 分钟前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot
Youkiup35 分钟前
【linux 常用命令】
linux·运维·服务器
qq_4336184436 分钟前
shell 编程(二)
开发语言·bash·shell
qq_2975046139 分钟前
【解决】Linux更新系统内核后Nvidia-smi has failed...
linux·运维·服务器
charlie1145141911 小时前
C++ STL CookBook
开发语言·c++·stl·c++20