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、总结


一、环境信息

名称
CPUIntel(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源码

#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)函数介绍

函数名readwrite
头文件#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源码

#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)函数介绍

函数名getcputc
头文件#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源码

#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)函数介绍

函数名fgetcfputc
头文件#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源码

#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)函数介绍

函数名fgetsfputs
头文件#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

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、编译

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

[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表示显示以字符方式打印内容。

[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

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

[gbase@czg2 Src]$ du -sh /home/gbase/TestData.sql 
1.2G    /home/gbase/TestData.sql

8、对比测试

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

(1)操作系统cp

[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 

[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 

[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

[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

[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标准库,方便移植推荐。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值