《Linux 高级编程》

文章目录

    • linux 常用工具简介:
      • tar打包器---解压缩指令:
      • 常用命令:
      • 可执行文件查看(代码段,数据段,bss段):
      • 堆和栈的区别:
    • 常见内存错误说明:
    • 内存分配相关理解:
      • 1.内存分配方式:
      • 2.动态分配常用函数及说明:
        • 2.1 malloc & free:
        • 2.2 realloc:
        • 2.3 calloc():
        • 2.4 alloca ():
      • 3 内存常用管理函数:
        • 3.1 memcpy():
        • 3.2 memmove():
        • 3.3 memset():
        • 3.4 memchr()
        • 3.5 memcmp():
    • 文件与文件流及相关操作函数:
      • 1.文件存储及操作分类:
      • 2.标准流及流主要功能:
      • 3.文件流指针:
      • 4.缓冲区类型及指定:
        • 4.1缓冲区分类:
        • 4.2 缓冲区指定函数:
      • 5 ANSI C文件I/O操作(对系统调用再次封装):
        • 5.1 fopen()打开文件:
        • 5.2 关闭文件fclose() /fcloseall():
        • 5.3 更新缓冲区内容fflush:
        • 5.4 字符读操作:
        • 5.5 字符写操作,标准流中写一个字符:
        • 5.6 行读出操作:
        • 5.7 行写入操作:
        • 5.8 块读出操作:
        • 5.9 块写入操作:
        • 5.10 feof() 文件末尾检测函数:
        • 5.11 ferror() 函数判断给定流是否错误:
        • 5.12 clearerr() 清除错误标识位
        • 5.13 ftell() 返回当前读写位置:
        • 5.14 fseek() 修改当前读写位置。
        • 5.15 rewind() 重置当前读写位置
        • 5.16 printf/scanf 函数:
        • 5.17 sprintf() 函数:
        • 5.18 sscanf() 函数:
      • 6 POSIX 标准文件及目录管理:
        • 6.1 基础说明
        • 6.2 文件表结构:
        • 6.3 文件描述符相关操作函数:
          • 6.3.1 fileno()
          • 6.3.2 fdopen()
        • 6.4 POSIX 标准下文件IO管理:
          • 6.4.1 open() 打开文件:
          • 6.4.2 close() 关闭文件:
          • 6.4.3 creat() 创建文件
          • 6.4.4 fcntl() 修改文件描述符的特殊属性:
          • 6.4.5 read() 从指定文件读取指定大小的数据
          • 6.4.6 write向文件写数据
          • 6.4.7 lseek() 函数文件定位:
          • 6.4.8 sync & fsync & fdatasync :
          • 6.4.9 mmap()映射文件到内存:

《Linux 高级程序设计》

  • UNIX 操作系统于1969 年在AT&T贝尔实验室实现。收取少量费用供其它开发者使用修改。

  • 加州大学伯克利分校计算机系统研究小组CSRG 借助UNIX 对操作系统研究,改进,包括撰写更好的内存管理等,组成完整的BSD-UNIX 系统向外发行。

  • Linux 由操纵系统发展而来,由Linux Torvalds 和网络黑客从0编写。是一套开源代码,但遵循GNU 的GPL.(因为不限制商家对自由软件进一步开发,因此出现ubutu, redhat 等多个linux发行版)

  • GUN 项目开发很多高质量编程工具(如emacs,gcc,g++),所有GNU软件和派生工作均使用GNU 通用公共许可证(GPL)。

  • posix 标准表示可移植操作系统接口,不局限于UNIX 。posix 提供源代码级别的C语言应用编程接口,如read/write. posix 1.0 和 posix2.0 分别定义了兼容操作系统的C语言系统接口以及工具标准。

  • 库函数和系统调用:库函数完成常见功能,系统调用通常与操纵系统有关,库函数最终一般还是会调用系统函数。系统函数会从用户模式切换为内核模式,然后使用系统资源。

  • glibc 函数库:c语言并没有为常见的操作,如输入/输出,内存管理等提供内置支持。这些功能一般由标准函数库来提供。GNU 的函数库glibc 是Linxu 最重要的函数库,定义ISOC 标准所有库函数以及posix附加特色及GNU其他扩展。

  • 在线文档查询: 命令行man xxx, 工具软件则是用info(m/n/p/q 等指令).

  • 获取错误信息方法:当系统调用出错时会返回-1, 错误信息记载在全局errno 中。 有关errno定义在/usr/include/asm/errno.h 中。

    ERERM 0 :没有操作权限
    ENOENT 1:文件或目录不存在
    ESRCH 3:没有此程序
    EINTR 4:系统调用中断
    EI0 5:I/0错误

    错误打印输出函数,一般使用perror(“hxg”); 后面自动会将错误信息打印输出。也可以使用strerror 将错误打印到标准输出。

linux 常用工具简介:

tar打包器—解压缩指令:

解压缩指令

  • tar -cvf filename.tar xxxfile //将xxxfile 打包成filename.tar
  • tar -xvf filename.tar //解压打包文件
  • tar -cjvf filename.tar.bz2 xxxfile //将xxx file 用bz2 压缩并打包
  • tar -xjvf filename.tar.bz2 //强filename.tar.bz2 解压缩
  • tar -czvf filename.tar.gz directory/file 将file用gzip压缩并打包
  • tart -xzvf filename.tar.gz //解压一个gzip格式的Tar 文件。

常用命令:

  1. expand 如expand -t 4 hello.c 将hello.c 的制表符设置为4个空格
  2. grep 搜索字符串
    1. -i: 不区分大小写
    2. -I:只首次匹配
    3. -n: 输出前加匹配串所在行的行号
  3. find 查找文件

可执行文件查看(代码段,数据段,bss段):

gcc -o test hello.c

$: ls test -l 查看文件test的属性

$: file test 列出ELF 格式; ELF 格式可执行文件存储时分为代码区(text),数据区(data)和未初始化数据区bss 3个部分。 也可以readelf -a test 读取test 详细的elf 信息。

  • 代码区:CPU可执行的指令,通常为只读。被const声明的变量以及字符串常量在代码段中申请空间。
  • 数据段:已被初始化的全局变量或已初始化的静态变量(静态全局变量和静态局部变量),或者数据常量。
  • BSS 区(未初始化的数据区):未初始化的全局变量和静态变量。
  • 局部变量是运行中在栈中分配。

堆和栈的区别:

  • 管理方式不同:栈操作由系统自动管理完成,而堆工作由程序员管理控制
  • 空间大小不同:系统预先设定栈顶和大小,当申请空间超出栈剩余空间时,将出现栈溢出错误。堆是向搞地质扩展,不连续的内存。
  • 产生碎片不同:频繁使用malloc、free 会产生大量碎片,使得程序效率降低。
  • 分配方式不同:堆由malloc 和free 分配释放,而栈动态分配有alloca() 完成且其由编译器自动申请和方式,无需手工。
  • 分配效率不同: 堆分配效率较低。

常见内存错误说明:

  • 拒绝返回局部变量地址。
  • 操作系统加载某个应用程序时,分配一定大小的栈空间。避免申请过大局部变量以出现栈溢出。
  • 动态内存管理常见错误:
    • 申请和释放函数不一致! 使用C申请就是用C释放,使用C++申请就使用C++释放
    • 申请和释放大小不一致! 申请多少就释放多少
    • 释放后仍然读写! 释放后内存不应再读写。

内存分配相关理解:

1.内存分配方式:

  • 静态分配:编译器在编译源代码时分配,如全局和静态变量。程序执行前分配,效率高。可直接使用变量名字操作。
  • 动态分配:程序执行时调用malloc() 库函数申请。程序执行时分配,效率低。使用指向内存的指针是使用动态内存的唯一方法。

2.动态分配常用函数及说明:

2.1 malloc & free:

说明

  • malloc()分配连续存储的空间,返回该空间的首地址;当剩余空间不足时,返回NULL

  • free()与malloc()配套使用,用于释放malloc分配的内存。

特点:

  • malloc 分配的内存使用完需要使用free释放
  • 不要使用free释放非malloc 分配的内存
  • 内存首地址返回的指针不要进行加减操作再free,否则会引发问题
  • 不能两次释放相同的指针
  • 内存释放后,指向内存的指针应该被赋值为NULL。
2.2 realloc:

说明

  • 在堆中更改已经分配的内存空间。extern void *realloc(void *ptr,size_t size);

特点:

  • 当前内存段后面拥有足够内存空间则直接扩展这段内存空间,返回原指针
  • 如果当前内存段后空闲字节不够,就重新寻找满足的内存块,并将目前数据copy到新位置,原始数据块释放(即释放原来指针realloc()的参数指针),返回新内存块地址。
  • 如果申请失败,则返回NULL.
2.3 calloc():

说明

  • 是malloc的简单包装,特点是将动态分配的内存初始化为0.
2.4 alloca ():

说明

  • 在栈中分配size个字节的内存空间,函数返回时自动释放掉该空间。

3 内存常用管理函数:

3.1 memcpy():
extern void* memcpy(void* des, const void* src,size_t n)
  • 从src copy n 个字节数据到des地址。
  • 类似strncpy()只是strncpy() 参数为char* 类型。
  • 类似bcopy(), 只是bcopy 多用于网络编程中。
3.2 memmove():
extern void* memmove(void* dest, void* src,size_t n)
  • 类似memcpy ,当dest 和src 没有重合时,与memcpy相同
  • 当dest与src 有重叠时,先处理再将src copy n 字节到dest.
3.3 memset():
extern void * memset(void *s, int _c, size_t _n)
  • 将s开始后面n位的值设置为c,执行成功返回s首地址。
  • 类似bzero()函数,bzero(s,n); 将s开始n字节设置为0
3.4 memchr()
extern void* memchr(const void* _s, int _c,size_t _n)
  • 在s中的前n个字节中查找c第一次出现的位置
3.5 memcmp():
extern int memcmp(const void* s1, void* s2, size_t _n)
  • 比较s1 和 s2 前n个字节是否相同
  • s1=s2 返回0, s1s2 返回1
  • 类似strncmp() 函数

文件与文件流及相关操作函数:

1.文件存储及操作分类:

文件存储分类

  • 文本文件:ASCLL文件,文本文件存储量大,速度慢。文件以EOF结束
  • 二进制文件:存量小,速度快,便于存放中间结果

文件操作分类

  • 缓冲文件操作:在用户空间自动为使用的文件开辟内存缓冲区,通过一次系统调用读取较多数据到缓冲区,这样避免每次读写都要进行系统调用。(系统调用会将CPU从用户态切换至内核态,影响效率)。
  • 非缓冲文件操作:最多只能在程序中开启缓存空间,每次读写都是一次系统调用。

2.标准流及流主要功能:

  • Linux 系统中,系统默认为每个进程打开3个文件,即每个进程默认可以操作3个流,标准输入流(/dev/stdin),标准输出流(/dev/stdout),标准错误输出流(/dev/stderr).
  • 每个进程从标准输入流读数据,向标准输出流写数据,向标准错误输出流写错误信息。
  • 可以通过重定向,修改输入流/输出流。

3.文件流指针:

在应用编程层面,程序对流的操作实际就是对文件流指针FILE的操作。操作一个文件前通过fopen()打开一个文件,返回该文件流指针且该指针与该文件关联。该文件流指针是一个文件描述符,对应的结构体可在用户空间进行访问。

4.缓冲区类型及指定:

4.1缓冲区分类:
  • 全缓冲区:该缓冲区默认大小与系统定义有关,大小定义在/usr/include/libio.h。 在缓冲区满或者调用刷新函数fflush() 后才进行I.O系统调用操作。普通磁盘文件的流通常使用全缓冲区访问。
  • 行缓冲区:行大小根据系统有关,通常默认128字节。当遇到换行符或缓冲区满时,行缓冲才刷新。终端使用行缓冲区古。
  • 不带缓冲区:标准I/O库不对字符进行缓存。标准出错流stderr 通常是不带缓冲区的,以便错误信息能够尽快显示出来。

使 用 缓 冲 区 , 不 需 要 每 次 进 行 标 准 I / O 处 理 时 都 使 用 系 统 I / O 调 用 \color{red}{使用缓冲区,不需要每次进行标准I/O处理时都使用系统I/O调用} 使I/O使I/O

4.2 缓冲区指定函数:

setbuf():

extern void setbuf(FILE* __stream,char* __buf);
  • 第一个参数为要操作的流对象
  • 第二个参数是指向BUFSIZ长度的缓冲区。如果buf设置为NULL,则关闭缓冲区。
  • 执行成功范围0,否则返回非0

setvbuf():

extern int servbuf(FILE* __stream, char* __buf, int __modes, size_t __n);
  • 第一个参数为要操作的流对象
  • 第二个参数buf指向一个长度为n大小的缓冲区
  • 第三个参数为缓冲区类型 ;#define _IOFBF 0 全缓冲;#define _IOLBF 1 行缓冲;#define _IONBF 2 无缓冲。
  • 如果指定不带缓冲区的流,则buf和n参数都忽略;如果buf=NULL,则系统默认分配适当大小的缓存区。

5 ANSI C文件I/O操作(对系统调用再次封装):

5.1 fopen()打开文件:
extern FILE* fopen(__const char* __filename,__const char* __modes);
  • 第一个参数为打开文件的绝对路径或者相对路径。
  • 第二个参数为打开方式:具体如下:
    《Linux 高级编程》_第1张图片
  • 函数执行成功则返回文件指针,如果文件打开失败则返回NULL
5.2 关闭文件fclose() /fcloseall():

fclose():

extern int fclose(FILE* __stream)
  • 参数为文件流指针
  • 当close文件时, 操 作 系 统 会 自 动 将 缓 冲 区 的 内 容 回 写 到 文 件 \color{red}{操作系统会自动将缓冲区的内容回写到文件}
  • 成功返回0,否则返回EOF, 并设置errno 全局变量。

extern int fcloseall(void);
  • 关闭所有流对象。如进程关闭时,需要关闭打开的所有流对象。
5.3 更新缓冲区内容fflush:

通过I/O系统调用将缓冲区内容写会磁盘中。

extern int fflush(FILE* __stream);
  • 函数执行成功返回0,否则返回EOF,并设置标准错误error
5.4 字符读操作:
extern int fgetc(FILE * __stream); //从流中读一个字符
extern int getc(FILE *__stream);   //#define getc(_fp) _IO_getc(_fp)
  • 每次系统调用只从流中读出一个字符。
  • 返 回 无 符 号 c h a r 类 型 强 制 转 换 为 i n t 型 \color{red}{返回无符号char 类型强制转换为int型} charint
  • 成功返回读的内容,失败或文件结束则返回EOF(-1)
  • feof() 检测是否督导结束
  • ferror() 检测是否出错
  • 比如fgetc(stdin) 从标准输入流读一个字符。标准输入读一个字符还可以使用getchar() //extern int getchar(void);
5.5 字符写操作,标准流中写一个字符:
extern int fputc(int __c, FILE* __stream)  //将字符c写到流stream中
extern int putc(int __c, FILE* __stream)
  • 向标准输出流写一个字符可用putchar()相当于fputc(c,stdout). //extern int putchar(int __c);
  • 调用成功返回内容,失败返回-1
5.6 行读出操作:
extern char* fgets(char* __s, int __n, FILE *__stream)
  • 从strean 中读取n-1个字符存放到s中。
  • 成功返回s,失败(到文件尾部或出错)则返回NULL,
5.7 行写入操作:
extern int fputs(__const char* __s, FILE* __stream)
extern int puts(__const char* __s)  //输出流到标准输出设备中
  • fputs 将s指向的以空字符结尾的字符串写入stream中
  • puts()将s指向的以空字符结尾的字符串写入标准输出
  • 成功返回非负,失败返回-1
5.8 块读出操作:
extern size_t fread(void* __ptr,size_t __size, size_t __n, FILE* __stream)
  • 从stream 中读取n个大小为size的对象,存放在ptr所指的内存空间。
  • 返回实际读取到对象的个数,如果返回值
5.9 块写入操作:
extern size_t fwrite(__const void* __ptr, size_t __size, size_t __n, FILE *__S);
  • 从ptr 中读取n个大小为size的对象写入s指向的stream中。
  • 执行成功,函数返回实际写入的对象个数。
5.10 feof() 文件末尾检测函数:
extern int feof(FILE* __stream)
  • ASCLL文件可以直接根据返回值是否=EOF判断
  • 二进制文件需要使用该函数,返回1表示读到文件结束,否则返回0
5.11 ferror() 函数判断给定流是否错误:
extern int ferror(FILE* stream)
  • 没有错误将返回0
  • 否则返回错误符
5.12 clearerr() 清除错误标识位
extern void clearerr(FILE* __stream)
  • feof 或ferror 执行后,如果出现错误,将设置错误标识符,执行错误处理后需要清除错误标识符。
5.13 ftell() 返回当前读写位置:
extern long int ftell(FILE* __stream)
  • 成功返回流的当前读写位置距离文件开始的字节数
  • 失败返回-1
5.14 fseek() 修改当前读写位置。
extern int fseek(FILE* __stream, long int __off, int__whence)
  • 第一个参数为stream 流
  • 第二个参数为基于修改基础的偏移量
  • 第三个参数为修改位置的基准;SEEK_SET :文件开始;SEEK_CUR:当前位置;SEEK_END:文件结束位置
5.15 rewind() 重置当前读写位置
extern void rewind(FILE * __stream)

将读写指针移动到文件开头

案例:

FILE* fp_src;
fp_src=fopen("/mytest.cpp",r+);
if(fp_src==NULL)
	perror("error\n");

do{
	num=fread(buf, 1, 128, fp_src);  //读取128字节到buf中
	if(feof(fp_src)==1)
	break;
}while(1)

fclose(fp_src);
5.16 printf/scanf 函数:
int printf(const char*, ...)  // 按格式打印到标准输出
int scanf(const char*,...)	//按格式从标准输入获取

printf() 参考

5.17 sprintf() 函数:
int sprintf(char *buffer, const char *format[,argument]...)  //将第三个及之后参数按照format 格式输出到buffer指向的内存中。

  • 数字字符串操作: sprintf(s, “%d”,123);
  • 控制浮点数打印格式:sprintf(s,"%f",3,1415923);
  • 连接字符串:char* who; char * whom; sprintf(s, “%s love %s”,who,whom);
    sprintf 参考
5.18 sscanf() 函数:
extern int sscanf(__cosnt char* __s, char* foramt_at,...)
  • 从s指向的流中按照format格式读取数据到str.
  • 提取指定长度的字符串:sscanf(“12345”, “%4s”,str)
  • 提取包含1-9和小写字母的字符串:sscanf(“12345abcdABCD”,"%[1-9a-z]",str);
  • 取到到大写字母为止的字符串:sscanf(“12345abcdeABCDE”,"%[^]",str);
    sscanf() 参考

6 POSIX 标准文件及目录管理:

6.1 基础说明
  • ANSIC 的库函数主要方便用户层使用,这些库函数是对直接IO系统调用的封装,访问时根据缓冲区类型,减少直接I/O系统调用的次数。
  • POSIX 标准的IO管理是Linux 操作系统自身提供IO的系统调用,直接进行IO系统调用的移植性差。
  • ANSIC打开一个文件,在用户层有FILE 流及相关结构,实际调用系统调用时,在LINUX 内核中也会创建struct file 结构(存储挂载信息,读写位置,使用此结构的进程,文件打开模式等)对应已打开文件的信息。
  • 对于用户空间而言,任何一个流都对应一个唯一描述符。比如STDIN 对应0,STDOUT 对应1, stderr 对应2.
6.2 文件表结构:

每个进程有进程私有结构体,struct task_struct,其包含管理打开文件信息的表项,如下图(进程打开文件的内核数据结构):
《Linux 高级编程》_第2张图片

6.3 文件描述符相关操作函数:

每个stream 流对应唯一的文件描述符,以下是提供给用户层的接口。

6.3.1 fileno()
extern int fileno(FILE*_stream)
  • 根据stream 流获取对应的文件描述符。
  • 成功返回文件描述符值,失败返回-1
6.3.2 fdopen()
extern FILE* fdopen(int __fd, __const char* __modes)
  • 通过文件描述符获取流对象
  • 第一个参数为文件描述符,第二个参数以何种方式打开fd.
6.4 POSIX 标准下文件IO管理:

根据不同文件类型,相同的read/write 实际上操作的代码是不一样的。

6.4.1 open() 打开文件:
extern int open(_const char* __filepath,int _oflag,...)
  • 以某种方式打开某路径文件
  • 打开成功返回文件描述符,打开失败返回-1
  • 文件打开flag 参数如下, 该 参 数 表 示 当 前 进 程 对 该 文 件 的 访 问 权 限 \color{red}{该参数表示当前进程对该文件的访问权限} 访
    《Linux 高级编程》_第3张图片
  • 如果创建新文件有第三个参数,第三个参数决定创建的文件的权限,如777
    《Linux 高级编程》_第4张图片
6.4.2 close() 关闭文件:
extern int close(int __fd);
  • 根据打开文件获取的fd 关闭该文件
  • 调用成功返回0,否则返回-1
6.4.3 creat() 创建文件
extern int creat(__const char* _file, _mode_t __mode)
  • 第一个参数为创建文件的路径
  • 第二个参数为创建文件的权限
6.4.4 fcntl() 修改文件描述符的特殊属性:

fcntl() 参考

extern int fcntl(int __fd, int __cmd,...);
  • 第一个参数为修改属性的文件描述符
  • 第二个参数cmd为相应操作
  • 如设置属性,成功返回0,错误返回-1;如读取属性,成功返回属性值,错误返回-1

第二个参数说明

参数 说明
F_DUPFD 复制文件描述符,该动作仅仅在当前进程打开的文件表项新增一项,两者同时指向内核为该文件分配的文件表项
F_GETFD 获取与文件描述符关联的close-on_exc标志
F_SERFD 设置文件描述符关联文件的属性,第三个参数为对应属性
F _ G E T F L \color{red}{ F\_GETFL } F_GETFL 获取文件状态标志和访问模式
F_SERFL 修改文件状态标志及访问模式
  • F_SETFL支持的标志如下:
    《Linux 高级编程》_第5张图片
  • F_SETFD 仅仅修改当前文件描述符信息,而F_SETFL 修改与该文件相关的所有文件描述符。
6.4.5 read() 从指定文件读取指定大小的数据
extern ssize_t read(int __fd, void *_buf, size_t _nbytes)
  • 从参数fd 所指的文件读取nbutes 字节到buf中
  • 在读数据时,读位置会自动移动
  • 实际读取小于预读取数据量:
    1. 文件剩余字节小于nbyte
    2. read()请求被某个信号中断
    3. 文件是有名管道或无名管道
  • 读操作行为不同说明:
     支持搜索的文件(常规文件,目录文件,链接文件)从关联文件地址偏移量开始读取
     不支持搜索的文件,如字符设备文件,中断则总是从当前位置读取。
6.4.6 write向文件写数据
extern ssize_t write(int __fd, __const __buf, size_t n);
  • 从buf 读取n个字节写到fd
  • 写行为与实际操作的文件有关
6.4.7 lseek() 函数文件定位:
extern __off_t lseek(int _fd, _off_t _offset, int _whence)
  • fd 为打开的文件

  • whence参数为参考位置;SEEK_SET 0 :文件起始位置;SEEK_CUR 1:文件当前位置;SEEK_END 2: 文件结束位置。

  • offset 参数为相对参考位置的偏移量

    6.4.8 sync & fsync & fdatasync :

    写数据到文件时,通常由内核将数据复制到缓冲区,当缓冲区满或刷新缓冲区时,才会将数据写入输出队列,待其到队首时,才进行实际的I/O操作。因此需要一些函数更新缓冲区。
    sync() 函数:

#include 
void sync(void)
  • 函数sync 始终成功,只将所有修改过的块的缓存排入写队列,不等待时间I/O操作。
    fsync() 函数介绍:
int fsync(int fildes)
  • fsync 多用于数据库相关的应用程序。将修改过的块的缓存排入写队列,并等待I/O结束。
  • 成功返回0,否则返回-1

fdatasync() 函数

int fdatasync(int  fildes);
  • 只更新内容,没有必要不更新元数据。
  • 成功返回0,否则返回-1;
6.4.9 mmap()映射文件到内存:
#include <sys/mman.h>
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
  • 在进程的虚拟地址空间起始地址为start 长度为length 与fd 开始offset 起始形成映射。
  • 进程无需再调用read 和write 直接可操作映射区文件
  • flag 为映射的内存权限,不能与文件打开的权限冲突。
  • 修改映射区内容不会立马写回文件,需要使用msync() 实现
  • 取消映射时,需要使用munmap() 函数。

你可能感兴趣的:(linux,基础,c++,开发语言,后端)