Linux应用层开发快速入门自用版(持续更新)

基础部分

GCC、glibc、GNU C

总的来说,GCC是编译器,负责将源代码转换为可执行代码;glibc是运行时的库,提供程序运行所需的标准函数和操作系统服务的接口;而GNU C则定义了GCC支持的C语言的标准和扩展。

GCC的编译

简单介绍

例:要编译main.c和hello.c两个文件,编译后的文件命名为main。

则终端输入gcc main.c hello.c -o main。执行该编译则输入./main即可

gcc +要编译的文件(可多个) -o + 最后输出的文件名

程序流程:预处理-编译-汇编-链接

常用选项

 -c,预处理,编译(语法错误在此进行),汇编。但不链接。 

如何编译多个文件?

1.分开编译,统一链接:

gcc -c -o  main.o  main.c

gcc -c -o  sub.o    sub.c

gcc -o test main.o  sub.o

2.一起编译,链接

gcc  -o  test   main.c  sub.c

gcc -o test  a.c  b.c  

先.s文件,然后是.o文件,最后合并链接成为输出文件。全部重新编译太费时间,当个别文件修改时,建议分开编译。

main函数解析

参数说明

argc:argument count 参数个数

argv:argument value 参数值

补充知识

假设当前的编译文件名为hello,执行操作为:./hello

.表示当前目录,/表示当前目录的hello文件

在终端时:使用空格分隔参数。如果使用双引号的,则表示一个参数。

makefile基础

规则

目标:依赖文件(可多个)

tab  :需要执行的命令

--------------------------------------------------------------------------------------------------

说明:当“目标文件不存在”或者某个依赖文件比目标文件新时,则执行命令。

语法

通配符:%.o

$@目标文件,$<(第一个依赖文件)

$^(表示所有的依赖文件)

make[目标],若无目标,默认第一个目标

假想目标:.PHONY

#为注释

即时变量与延时变量:

A := xxx   #A的即刻确定,在定义时就确定好了

B = xxx    #B的在用到的时候才确定,不确定时为空

:=  #即时变量

=   #延时变量

?= #延时变量,如果是第一次定义才起效,如果前面已经有定义则这句被忽略

+= #附加,它是即时变量还是延时变量取决于前面的定义

案例。

补充:也可以通过终端输入传入变量。

结果如下:

makefile函数

定义变量

换行显示时要加 \

自动推导

伪目标声明

系统默认伪目标可以不声明

忽略错误和名称对应


文件IO

文件IO基础

基础概念(静态文件,PCB,文件描述符...)

  • 静态文件:磁盘中的文件。
  • 扇区:磁盘存储的最小单位,一般扇区为512字节也就是0.5K。
  • 一个块 : 多个扇区。1个块 = 8个扇区 (4K)

磁盘分区:innode区,数据区(文件内容)。

innode区存放innode表。每一个文件都对应一个innode节点。

open()函数通过文件路径得到innode编号,再找到对应innode,获取文件相关的信息。

通过ls -il 可以查看innode编号。

PCB(process control block)进程控制块。存储着许多进程的相关信息,用于管理进程。

错误编号(errno)

errno变量

errno 是一个整型变量,通常定义在 <errno.h> 头文件中,并且在 <stdio.h><stdlib.h> 等标准库头文件中也会包含这个定义。它的作用是在标准的函数调用失败时提供错误代码,帮助程序员识别问题并进行相应处理。

使用方法
  1. 错误检测: 当调用某些函数(如文件操作、内存分配、系统调用等)失败时,这些函数会设置 errno 变量来指示错误的类型。例如,open()malloc()read() 等函数在失败时会设置 errno

  2. 错误值检查: 在检测到函数失败后,可以通过检查 errno 的值来确定失败的具体原因。标准头文件 <errno.h> 中定义了一系列可能的 errno 值,如 EACCES(权限不足)、ENOMEM(内存不足)等。

  3. 重设: 在每次成功函数调用之前,errno 的值会被清零或重设,以确保它只反映最近一次失败调用的错误。

strerror()函数

strerror() 函数是一个标准C库函数,用于将 errno 错误码转换成对应的错误消息字符串。让我详细解释一下这个函数的作用和使用方法。

#include <string.h>

char *strerror(int errnum);

perror()函数

示例:

perror("open error"); 它会在输入的字符串后面拼接上程序错误的原因。

空洞文件

概念

文件的一部分区域没有数据

查看空洞文件

ls查看的是逻辑大小。du查看的是文件实际在磁盘中的存储大小。

O_TRUNC和O_APPEDN标志

O_TRUNC标志

截断文件,丢弃文件中的内容,将文件大小变为0。open函数

O_APPEDN标志

保证每次调用write()时都是从文件末尾开始写入。移动指针和写操作都是原子操作(整个过程不能被分割)。

注意:O_APPEDN标志对read操作不会有影响;lseek操作不会影响write操作

Makefile设置

系统调用

open()函数

函数介绍

作用:可用于打开文件(2个参数),创建文件(3个参数)

可变参:可传入2个参数,也可传入3个参数,主要取决于是打开文件还是创建文件。

参数1:文件路径名

参数2:打开文件的模式

参数3(可选):创建文件的权限

函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
  • pathname:表示要打开或创建的文件路径名。
  • flags:控制文件打开方式和行为的标志位,例如读取、写入、追加等。
  • mode:仅在创建新文件时才使用,指定新文件的权限位。

返回值

成功时返回文件标识符,失败返回-1.

权限分析

U:文件所有者,G:同组用户 ,O:其他用户。

rwx。r:可读,w:可写,x:可执行。777则表示UGO用户都是可读可写可执行。

 案例

open函数打文件时,文件指针位置默认为0。

 write()函数

函数介绍

write 函数是Linux系统编程中用于向文件描述符(或称为文件描述符表项)写入数据的系统调用。

函数原型
#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

参数解释:

  1. fd:文件描述符

    • 表示要写入数据的目标文件或设备。可以是打开文件的返回值,也可以是标准输出(STDOUT_FILENO,值为1)、标准错误(STDERR_FILENO,值为2)等预定义的文件描述符。
  2. buf:数据缓冲区

    • 是一个指向存放要写入数据的内存区域的指针。
  3. count:写入的字节数

    • 指定从缓冲区写入到文件描述符的字节数。

返回值:

  • 如果成功写入,则返回写入的字节数(正整数)。
  • 如果出错,则返回-1,并设置 errno 表示错误类型。

read和write函数共用一个指针。

案例:

 read()函数

函数介绍

read 函数是Linux系统编程中用于从文件描述符(或称为文件描述符表项)读取数据的系统调用。

函数原型
#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

参数解释:

  1. fd:文件描述符

    • 表示要从中读取数据的文件或设备。可以是打开文件的返回值,也可以是标准输入(STDIN_FILENO,值为0)等预定义的文件描述符。
  2. buf:数据缓冲区

    • 是一个指向存放读取数据的内存区域的指针。
  3. count:读取的字节数

    • 指定从文件描述符读取到缓冲区的最大字节数。

返回值:

  • 如果成功读取,则返回读取的字节数(非负整数)。
  • 如果已到达文件末尾(EOF),则返回0。
  • 如果出错,则返回-1,并设置 errno 表示错误类型。

close()函数

函数介绍

close 函数是Linux系统编程中用于关闭一个打开的文件描述符的系统调用。

函数原型
#include <unistd.h>

int close(int fd);

参数解释:

  1. fd:文件描述符
    • 表示要关闭的文件或设备的文件描述符。

返回值:

  • 如果成功关闭文件描述符,则返回0。
  • 如果出错,则返回-1,并设置 errno 表示错误类型。

lseek()函数(调整读写位置偏移)

函数介绍

lseek() 函数是用于在文件中移动读写位置(文件偏移量)的系统调用函数。它允许程序在打开的文件描述符上移动读写位置,以便随后的读取和写入操作可以在文件的任意位置进行。在 Unix-like 系统中,这是一个非常重要和常用的函数。

函数原型
#include <unistd.h>

off_t lseek(int fd, off_t offset, int whence);

参数解释:

  • fd:文件描述符,指向已打开文件的引用。
  • offset:偏移量,用于指定从 whence 参数所指示位置开始移动的距离。(可正可负)
  • whence:指定偏移量的起始位置,可以是以下几个值之一:
    • SEEK_SET:从文件开头开始计算偏移量。
    • SEEK_CUR:从当前文件偏移量位置开始计算偏移量。
    • SEEK_END:从文件末尾开始计算偏移量。

返回值:

  • 如果成功,返回新的文件偏移量(以字节为单位),相对于文件的起始位置(头部)。
  • 如果出错,返回 -1,并设置全局变量 errno 来指示具体错误原因。

移动到末尾时,可以计算文件的长度大小。

fcntl()函数

fcntl() 函数介绍

fcntl() 是一个用于操作文件描述符的函数。它可以用来获取或设置文件描述符的各种属性和行为。

函数原型
int fcntl(int fd, int cmd, ... /* arg */ );

参数
  • fd:文件描述符,指示要操作的文件。
  • cmd:命令,用于指定要执行的操作。
  • arg:依赖于 cmd 的可选参数,用于传递额外的信息。

常用命令

  • F_GETFD:获取文件描述符的标志。
  • F_SETFD:设置文件描述符的标志。
  • F_GETFL:获取文件状态标志(例如只读、只写等)。
  • F_SETFL:设置文件状态标志(例如设置文件为非阻塞模式)。

返回值
  • 成功时,返回具体的值或者文件描述符。
  • 失败时,返回 -1,并设置 errno

ioctl()函数

函数介绍

ioctl() 是一个用于设备控制的函数,可以用来发送控制命令到设备驱动程序。

函数原型
int ioctl(int fd, unsigned long request, ... /* argp */ );

参数
  • fd:设备文件的文件描述符。
  • request:控制命令,指定要执行的操作。
  • argp:命令的参数,通常是指向数据的指针,依赖于具体的 request 命令。

常用命令

  • 设备控制相关的命令,比如网络接口的配置、终端设置等。
  • 不同的设备驱动程序定义了不同的 request 命令和参数。

返回值
  • 成功时,返回 0 或其他具体值。
  • 失败时,返回 -1,并设置 errno

文件描述符(本质是个数字)

特点

  • 作为文件的句柄
  • 是一个非负整数
  • 与对应的文件相绑定

文件描述符的分配

分配一个没有被使用的最小非负整数作为文件描述符。

通过open()函数,多个文件描述符可以指向同一个文件,但一个文件描述符只能指向一个文件。

系统默认文件描述符

  • 0是标准输入stdin的文件描述符,
  • 1是标准输出stdout的文件描述符,
  • 2是标准错误stderr的文件描述符。

同一个文件被多次打开

文件描述符与文件的对应关系

 n对1。多个文件描述符可以对同一个文件进行读写操作

读写位置偏移量是相互独立

每个文件描述符都有各自的文件表,所以位置偏移量是各自维护自己的

使用多个文件描述符对同一文件进行写操作

可以使用O_APPEDN防止数据覆盖。

每一个文件描述符fd都要用close关闭

文件描述符的复制

文件描述符的复制原理

对fd进行复制,得到它的副本。它们使用的是同一个读写指针,权限也相同。

dup()函数

函数介绍

dup() 函数是 Unix/Linux 系统中的一个系统调用,用于复制文件描述符。它的主要作用是复制一个已有的文件描述符,使得两个文件描述符指向相同的文件表项(file table entry),从而共享文件状态和文件偏移量。

功能

  • dup(int oldfd)复制文件描述符 oldfd,并返回新的文件描述符。
  • dup2(int oldfd, int newfd)复制文件描述符 oldfdnewfd,如果 newfd 已经打开,则先关闭它

参数

  • oldfd:要复制的现有文件描述符。
  • newfd(仅适用于 dup2):新的文件描述符,如果指定的话。

返回值

  • dup() 返回新的文件描述符,如果失败则返回 -1,并设置 errno
  • dup2() 返回复制后的文件描述符(即 newfd),如果失败则返回 -1,并设置 errno

图解


文件共享

概念

同一个文件被多个进程或线程实现IO操作

文件共享的常见实现方式

多个进程间实现文件共享

  • 通过open()函数打开共享文件。
  • 通过dup复制文件标识符访问共享文件

同一进程的多个线程进行文件共享

  • open函数打开共享文件
  • 通过dup复制文件标识符访问共享文件

注意:通过复制的文件描述符指向的文件表是同一个。


原子操作与竞争冒险

原子操作

操作之间不可被分割,不可被打断。

  1. pread()和pwrite()函数
  2. O_APPEND标志
  3. O_EXCL标志(在open函数中与O_CREATE一起使用),防止创建两个相同的文件

截断文件

truncate() ftruncate() 是在文件操作中常用的函数,用于修改文件的大小。虽然它们的目的相似,但用法和适用场景有所不同。

truncate()函数

函数原型:
int truncate(const char *path, off_t length);
参数
  • path:要调整大小的文件的路径。
  • length:文件的新大小(以字节为单位)。如果这个值小于文件当前的大小,文件将被截断到这个长度;如果大于当前大小,文件会扩展,并在扩展的部分填充空白(通常是零)。

返回值
  • 成功时返回 0
  • 失败时返回 -1,并设置 errno 以指示错误原因。

ftruncate()函数

函数原型
int ftruncate(int fd, off_t length);

参数
  • fd:要调整大小的文件的文件描述符。文件描述符是在调用 open()creat() 函数时获得的。
  • length:文件的新大小(以字节为单位)。同样地,文件将被截断到这个长度,或者扩展以达到这个长度。
返回值
  • 成功时返回 0
  • 失败时返回 -1,并设置 errno 以指示错误原因。


标准I/O库

标准IO和文件IO之间的区别

  1. 标准IO是库函数,而文件IO是系统调用。文件IO:open,read,write...;标准IO:fopen,fread,fwrite...
  2. 标准IO是对文件IO的封装
  3. 标准IO比文件IO移植性更好
  4. 标准IO效率高于文件IO

FILE指针

标准IO使用FILE指针作为文件句柄,与文件IO中的文件描述符相似

fopen()函数

函数介绍

fopen() 用于打开一个文件,并返回一个指向 FILE 结构的指针,这个结构表示打开的文件。

函数原型
FILE *fopen(const char *filename, const char *mode);
参数
  • filename:要打开的文件名。
  • mode:文件的打开模式,例如 "r"(只读)、"w"(写入,文件不存在则创建)、"a"(追加)。

返回值
  • 成功时,返回一个指向 FILE 结构的指针。
  • 失败时,返回 NULL,并且可以通过 errno 查看具体错误。

fclose()函数

函数介绍

fclose() 用于关闭由 fopen() 打开的文件。关闭文件后,相关的 FILE 结构不再有效。

函数原型

  • int fclose(FILE *stream);
    
参数
  • stream:要关闭的 FILE 指针。

返回值
  • 成功时,返回 0
  • 失败时,返回 EOF,并且可以通过 errno 查看具体错误。

fread() 函数(读操作)

函数介绍

fread() 用于从文件中读取数据到内存中。

函数原型
size_t fread(void *ptr, size_t size, size_t count, FILE *stream);

参数
  • ptr:指向内存的指针,用于存储读取的数据。
  • size:每个元素的字节数。
  • count:要读取的元素数量。
  • stream:指向 FILE 结构的指针,表示要读取的文件。

返回值
  • 成功时,返回实际读取的元素数量(可能小于 count,例如遇到文件末尾)。
  • 失败时,返回 0

fwrite() 函数(写操作)

函数介绍

fwrite() 用于将内存中的数据写入到文件中。

函数原型
size_t fwrite(const void *ptr, size_t size, size_t count, FILE *stream);
参数
  • ptr:指向要写入文件的内存块。
  • size:每个元素的字节数。
  • count:要写入的元素数量。
  • stream:指向 FILE 结构的指针,表示要写入的文件。

返回值
  • 成功时,返回实际写入的元素数量(可能小于 count,例如磁盘满)。
  • 失败时,返回 0

fseek() 函数

函数介绍

fseek() 用于设置文件流中的当前位置(指针位置)。

函数原型
int fseek(FILE *stream, long offset, int whence);

参数
  • stream:指向 FILE 结构的指针,表示要操作的文件。
  • offset:相对于 whence 的偏移量(字节数)。
  • whence:起始位置,可以是 SEEK_SET(文件开头)、SEEK_CUR(当前位置)或 SEEK_END(文件末尾)。
返回值
  • 成功时,返回 0
  • 失败时,返回 -1,并且可以通过 errno 查看具体错误。

feof() 函数

函数介绍

feof() 用于检测文件流是否已经到达文件末尾(EOF)。

函数原型
int feof(FILE *stream);
参数
  • stream:指向 FILE 结构的指针,表示要检查的文件流。
返回值
  • 如果文件流到达了文件末尾,返回非零值(通常是 1)。
  • 如果文件流没有到达文件末尾,返回 0

ferror() 函数

函数介绍

ferror() 用于检测文件流是否发生了错误。

函数原型
int ferror(FILE *stream);
参数
  • stream:指向 FILE 结构的指针,表示要检查的文件流。
返回值
  • 如果文件流发生了错误,返回非零值(通常是 1)。
  • 如果文件流没有发生错误,返回 0

clearerr() 函数

函数介绍

clearerr() 用于清除文件流的错误标志和EOF标志,使文件流恢复正常状态。

函数原型
void clearerr(FILE *stream);

参数
  • stream:指向 FILE 结构的指针,表示要清除标志的文件流。

返回值
  • 该函数没有返回值。

格式化输出(写文件)

printf()

fprintf()

dprintf()

sprintf()

snprintf()

格式化输入(读文件)

scanf()

fscaanf()

sscanf()

文件I/O的内核缓冲

fadtasync()

sync()

直接IO可以绕过内核

open函数中使用O_DIRECT标志

直接I/O的对齐限制

  1. 应用程序的缓冲区起始地址必须以块大小的整数倍进行对齐
  2. 写文件时,文件的位置偏移量必须是块大小的整数倍
  3. 写入到文件的数据大小必须是块大小的整数倍

刷新stdio缓冲区

文件描述符与FILE指针互转

fdopen() 函数

函数介绍

fdopen() 用于将一个已打开的文件描述符(int 类型)与 FILE 结构体关联起来,从而可以使用标准 I/O 函数(如 fread()fwrite() 等)来操作该文件描述符。

函数原型
FILE *fdopen(int fd, const char *mode);
参数
  • fd:一个已打开的文件描述符,通常由系统调用如 open() 获取。
  • mode:一个字符串,指定打开文件流的模式(如 "r" 读取模式,"w" 写入模式等)。
返回值
  • 成功时,返回一个指向 FILE 结构的指针。
  • 失败时,返回 NULL,并设置 errno 以指示错误。

fileno() 函数

函数介绍

fileno() 用于将 FILE 结构体指针转换为对应的文件描述符(int 类型),这对于系统调用和底层文件操作很有用。

函数原型
int fileno(FILE *stream);
参数
  • stream:指向 FILE 结构的指针,表示要获取文件描述符的文件流。
返回值
  • 成功时,返回与 FILE 流相关联的文件描述符。
  • 失败时,返回 -1,并设置 errno 以指示错误。

文件属性与目录

文件类型(7种)

  1. 普通文件(regular file)
  2. 目录(directory)
  3. 字符设备(character)
  4. 块设备(block)
  5. 符号链接(link)
  6. 管道(pipe)
  7. 套接字(socket)

stat() 函数

函数介绍

stat() 函数是 Unix 和类 Unix 系统中用于获取文件信息的函数。它提供了有关文件的详细状态信息,这些信息对于文件操作和系统编程非常重要。下面是对 stat() 函数的深入讲解:

函数原型
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
参数
  • path:指向要查询状态的文件或目录的路径的字符串。
  • buf:指向 struct stat 结构的指针,用于存储函数获取到的文件状态信息。
返回值
  • 成功时,返回 0
  • 失败时,返回 -1,并设置 errno 以指示错误。

struct stat 结构体

struct stat 结构体包含了文件的多种属性,通常定义如下:

struct stat {
    dev_t     st_dev;     // 文件所在设备的 ID
    ino_t     st_ino;     // 文件的 i-node 号码
    mode_t    st_mode;    // 文件的类型和权限
    nlink_t   st_nlink;   // 硬链接的数量
    uid_t     st_uid;     // 文件的所有者的用户 ID
    gid_t     st_gid;     // 文件的所有者的组 ID
    dev_t     st_rdev;    // 如果是设备文件,则为设备 ID
    off_t     st_size;    // 文件的大小(字节)
    blkcnt_t  st_blocks;  // 文件占用的块数量
    time_t    st_atime;   // 最后访问时间
    time_t    st_mtime;   // 最后修改时间
    time_t    st_ctime;   // 状态最后更改时间
};

文件模式(st_mode

st_mode 字段中包含了文件的类型和权限信息。可以使用宏来解析这些信息,例如:

  • 文件类型
    • S_IFREG:常规文件
    • S_IFDIR:目录
    • S_IFCHR:字符设备
    • S_IFBLK:块设备
  • 权限
    • S_IRUSR:所有者有读取权限
    • S_IWUSR:所有者有写入权限
    • S_IXUSR:所有者有执行权限

fstat 函数

函数原型
#include <sys/stat.h>
int fstat(int fd, struct stat *buf);
参数
  • fd:文件描述符(int 类型),这是一个由 open()dup()socket() 等函数返回的文件描述符。
  • buf:指向 struct stat 结构的指针,用于存储文件状态信息。
返回值
  • 成功时,返回 0
  • 失败时,返回 -1,并设置 errno
使用场景
  • fstat 用于获取已经打开的文件描述符的状态信息。你不需要知道文件的路径,只需提供文件描述符即可。

lstat 函数

函数原型
#include <sys/stat.h>
int lstat(const char *path, struct stat *buf);
参数
  • path:要查询状态的文件或目录的路径。
  • buf:指向 struct stat 结构的指针,用于存储文件状态信息。

返回值
  • 成功时,返回 0
  • 失败时,返回 -1,并设置 errno
使用场景
  • lstat 用于获取符号链接本身的状态信息,而不是符号链接指向的目标文件的状态信息。与 stat 函数不同,lstat 不会跟随符号链接到实际的目标文件。

用户属主

chown函数

函数介绍

chown() 函数是 Unix 和类 Unix 系统中用于更改文件或目录所有者和所属组的一个系统调用。

函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
int chown(const char *pathname, uid_t owner, gid_t group);
参数
  • pathname:要更改的文件或目录的路径名(字符串)。
  • owner:新所有者的用户 ID(uid_t 类型)。如果不想更改所有者,可以设置为 -1
  • group:新所属组的组 ID(gid_t 类型)。如果不想更改所属组,可以设置为 -1
返回值
  • 成功时,返回 0
  • 失败时,返回 -1,并设置 errno
使用场景
  • 更改文件或目录的所有者:如果你是超级用户(root),你可以将文件的所有者更改为其他用 户。
  • 更改文件或目录的所属组:你可以将文件的所属组更改为其他组。
  • 同时更改所有者和所属组:通过设置两个参数,都可以进行更改。如果你只关心其中一个,可以将另一个参数设置为 -1

文件访问权限

access() 函数

函数介绍

access() 函数用于检查当前进程是否有权限访问指定的文件。它不会改变文件的权限,只是验证访问权限。

函数原型
#include <unistd.h>

int access(const char *pathname, int mode);

参数
  • pathname:要检查的文件路径。
  • mode:要检查的权限类型,可以是以下值之一(可以通过按位或组合多个值):
    • R_OK:检查文件是否可读。
    • W_OK:检查文件是否可写。
    • X_OK:检查文件是否可执行。
    • F_OK:检查文件是否存在。

返回值
  • 返回 0 表示请求的权限检查成功。
  • 返回 -1 表示请求的权限检查失败,并设置 errno 以指示错误原因。

chmod() 函数

函数介绍

chmod() 函数用于改变文件的权限模式,即设置文件的读取、写入和执行权限。

函数原型
#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);

参数
  • pathname:要更改权限的文件路径。
  • mode:新的权限模式,通常使用八进制数表示,如 06440755 等。
权限模式
  • 权限通常以 rwx 的形式表示(读、写、执行),并以八进制表示。例如:
    • 0400:文件拥有者可读。
    • 0200:文件拥有者可写。
    • 0100:文件拥有者可执行。
    • 0077:其他用户没有权限。
返回值
  • 返回 0 表示成功。
  • 返回 -1 表示失败,并设置 errno 以指示错误原因。

文件的时间戳

三个时间属性:st_atim,st_mtim,st_ctim

修改时间属性:utime()和utimes()

修改时间属性:futimens()和utimensat()

utime() 函数

utime() 函数用于设置文件的访问时间和修改时间。

函数原型
#include <utime.h>

int utime(const char *filename, const struct utimbuf *times);

参数
  • filename:要修改时间戳的文件名。
  • times:指向 struct utimbuf 结构体的指针,该结构体包含两个时间字段。

struct utimbuf 结构体定义如下:

struct utimbuf {
    time_t actime;  // 访问时间
    time_t modtime; // 修改时间
};

返回值

返回值为 0 表示成功,返回值为 -1 表示失败。如果失败,通常可以通过 errno 变量获取更多的错误信息。

utimes() 函数

函数介绍

utimes() 函数也是用来设置文件的访问时间和修改时间,但它允许使用更精确的时间(以秒为单位)来设置。

函数原型
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <utime.h>

int utimes(const char *filename, const struct timeval times[2]);

参数
  • filename:要修改时间戳的文件名。
  • times:一个包含两个 struct timeval 结构体的数组。第一个 struct timeval 用于访问时间,第二个 struct timeval 用于修改时间。

struct timeval 结构体定义如下:

struct timeval {
    time_t tv_sec;  // 秒
    suseconds_t tv_usec; // 微秒
};

返回值
  • 成功:返回 0,表示时间戳成功更新。
  • 失败:返回 -1,表示发生了错误。在这种情况下,可以通过 errno 变量来获取具体的错误原因。

futimens 函数

函数介绍

futimens 是一个用于更新打开文件描述符的时间戳的函数。它比 utime 更灵活,因为它允许你操作文件描述符而不是文件路径。

函数原型
#include <fcntl.h>
#include <time.h>

int futimens(int fd, const struct timespec times[2]);
参数
  • fd:文件描述符,表示要更新时间戳的文件。这个文件描述符必须是通过 open 函数获得的。
  • times:一个指向 struct timespec 结构体数组的指针,这个数组包含两个时间值:
  • times[0]:访问时间(struct timespec),表示你希望设置的访问时间。
  • times[1]:修改时间(struct timespec),表示你希望设置的修改时间。
struct timespec 结构体
struct timespec {
    time_t tv_sec;  // 秒数
    long tv_nsec;   // 纳秒数
};
返回值
  • 成功:返回 0
  • 失败:返回 -1,并设置 errno 以指示错误。

utimensat 函数

函数介绍

utimensat 是一个更灵活的函数,用于修改文件或目录的时间戳。它支持相对路径和绝对路径的处理,并且可以结合文件描述符使用,提供了更多的灵活性。

函数原型
#include <fcntl.h>
#include <time.h>

int utimensat(int dirfd, const char *pathname, const struct timespec times[2], int flags);
参数
  • dirfd:目录文件描述符。它用于相对路径的处理。如果 pathname 是相对路径,dirfd 就是相对路径的起点。通常情况下,这个参数可以设置为 AT_FDCWD(表示当前工作目录),当 pathname 是绝对路径时。
  • pathname:要修改时间戳的文件或目录的路径。如果 flags 包含 AT_SYMLINK_NOFOLLOW,则 pathname 是符号链接本身,而不是它所指向的目标。
  • times:一个指向 struct timespec 结构体数组的指针,表示两个时间戳(访问时间和修改时间)。
  • flags:标志位,可以是以下值之一:
    • 0:表示正常行为。
    • AT_SYMLINK_NOFOLLOW:表示 pathname 是符号链接本身,而不是符号链接所指向的目标。
返回值
  • 成功:返回 0
  • 失败:返回 -1,并设置 errno 以指示错误。

符号链接

  • 软链接:像快捷方式,指向原始文件的路径,原始文件删除后软链接失效。
  • 硬链接:是原始文件的另一种名字,删除其中一个链接不会影响文件内容,只有所有链接都被删除,文件数据才会被清除。

link 函数

功能

创建一个硬链接。

函数原型
int link(const char *oldpath, const char *newpath);

参数
  • oldpath:原始文件的路径。
  • newpath:新创建的硬链接的路径。

返回值
  • 成功时返回 0
  • 失败时返回 -1,并设置 errno

说明

  • 硬链接创建了指向同一文件数据的额外路径。
  • 文件内容对所有硬链接是共享的,删除一个硬链接不会影响其他链接。

symlink 函数

功能

创建一个软链接。

函数原型

int symlink(const char *target, const char *linkpath);
参数
  • target:原始文件的路径。
  • linkpath:新创建的软链接的路径。
返回值
  • 成功时返回 0
  • 失败时返回 -1,并设置 errno
说明
  • 软链接是一个独立的文件,内容是原始文件的路径。
  • 删除原始文件后,软链接会变成“断链”。

readlink 函数

功能

读取软链接指向的目标路径。

函数原型
ssize_t readlink(const char *path, char *buf, size_t bufsize);
参数
  • path:软链接的路径。
  • buf:存放软链接目标路径的缓冲区。
  • bufsize:缓冲区的大小。
返回值
  • 成功时返回软链接目标路径的长度。
  • 失败时返回 -1,并设置 errno
说明
  • 读取软链接的内容,不跟随链接访问实际文件。
  • 如果缓冲区不足,返回的长度可能会超过 bufsize

目录

普通文件由inode节点和数据库构成

目录由inode节点和目录块构成

 创建目录:mkdir()

功能

创建一个新目录。

函数原型
int mkdir(const char *pathname, mode_t mode);
参数
  • pathname:要创建的目录的路径。
  • mode:新目录的权限模式,指定了目录的访问权限。
返回值
  • 成功时返回 0
  • 失败时返回 -1,并设置 errno
说明
  • mode 参数指定新目录的权限,通常使用八进制表示,例如 0755 表示读、写和执行权限。
  • 目录的权限可以通过使用 chmod 命令或 fchmod 函数来修改。
  • 如果 pathname 指定的路径已经存在或无法创建目录(例如没有权限),mkdir() 函数将失败。

删除目录:rmdir()

功能

删除一个空目录。

函数原型
int rmdir(const char *pathname);

参数
  • pathname:要删除的目录的路径。

返回值
  • 成功时返回 0
  • 失败时返回 -1,并设置 errno

说明
  • 只能删除空目录,若目录不为空,调用 rmdir() 将失败。
  • 删除目录时,需确保目录的路径正确且具有足够的权限。

打开目录opendir()

功能

打开一个目录,并返回一个目录流,用于读取目录中的条目。

函数原型
DIR *opendir(const char *name);
参数
  • name:要打开的目录的路径。

返回值

  • 成功时返回指向 DIR 结构的指针,该指针表示打开的目录流。
  • 失败时返回 NULL,并设置 errno

说明

  • opendir() 用于打开目录以供读取操作。打开的目录流可以用于后续的 readdir()closedir() 等函数操作。
  • 如果指定的路径不是一个有效的目录或没有权限打开该目录,opendir() 将失败。

readdir() 函数

功能

读取目录中的下一个条目。

函数原型
struct dirent *readdir(DIR *dirp);

参数
  • dirp:指向 DIR 结构的指针,表示打开的目录流。

返回值
  • 成功时返回指向 dirent 结构的指针,该结构包含下一个目录条目的信息。
  • 如果已读取完所有目录条目或发生错误,返回 NULL,并设置 errno

说明
  • readdir() 用于从目录流中读取目录条目。返回的 dirent 结构包含有关目录条目的信息,例如文件名。
  • 读取完所有条目后,readdir() 将返回 NULL。若读取过程中发生错误,也会返回 NULL
  • 读取操作不会改变目录流的当前位置,后续调用 readdir() 将继续从当前位置读取条目。

closedir() 函数

功能

关闭由 opendir() 打开的目录流。

函数原型
int closedir(DIR *dirp);

参数
  • dirp:指向 DIR 结构的指针,表示要关闭的目录流。

返回值
  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno

说明
  • closedir() 用于释放 opendir() 分配的资源并关闭目录流。
  • 关闭目录流后,无法再使用该目录流进行读取操作。

删除文件:unlink()

功能

删除文件名及其对应的文件数据。

函数原型
int unlink(const char *pathname);

参数
  • pathname:要删除的文件的路径。

返回值
  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno
说明
  • unlink() 删除指定路径的文件。文件的实际数据会被删除,如果没有其他文件描述符引用该数据,数据也会从磁盘中移除。
  • 它不适用于目录,仅用于文件。

删除文件:remove()

功能

删除文件或空目录。

函数原型
int remove(const char *pathname);

参数
  • pathname:要删除的文件或目录的路径。
返回值
  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno

说明
  • remove() 既可以删除文件也可以删除空目录。对文件的处理方式类似于 unlink()
  • 删除目录时,目录必须为空,否则会失败。

文件重命名:rename()

功能

重命名或移动文件。

函数原型
int rename(const char *oldpath, const char *newpath);

参数
  • oldpath:当前文件的路径。
  • newpath:新的文件路径或新的文件名。
返回值
  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno

说明
  • rename() 可以用来改变文件名或将文件移动到不同的目录。
  • 如果 oldpathnewpath 在同一文件系统中,它会直接重命名文件;如果在不同的文件系统中,它会将文件复制到新位置并删除原文件。
  • 目录的重命名也适用,但不能移动目录到不同的文件系统。

字符串处理

字符串输出

putchar

puts

fputc

fputs

字符串输入

gets

getchar

fgetc

fgets

字符串拼接

strcat

strncat

字符串拷贝

strcpy

strncpy

memcpy

memmove

bcopy

内存填充

memset

字符串比较

strcmp

strncmp

字符串的查找

strchr

strchr

strtstr

strpbrk

字符串与数字互转

字符串转整型数据

atoi:将字符转换成int

atol:将字符转换成long

atoll:将字符转换成long long

..

字符串转浮点数据

atof:将字符转成float

数字转字符串

系统信息与资源

获取系统信息

uname()

函数原型
int uname(struct utsname *buf);
参数
  • buf:一个指向 struct utsname 结构的指针,用于接收系统信息。
返回值
  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno
结构体 struct utsname
struct utsname {
    char sysname[];    // 操作系统名称,如 "Linux"
    char nodename[];   // 主机名
    char release[];    // 操作系统版本
    char version[];    // 内核版本
    char machine[];    // 硬件架构,如 "x86_64"
};
说明
  • uname() 用于获取系统相关信息。比如,通过 sysname 可以知道操作系统类型(如 "Linux"),release 提供内核版本信息,machine 显示硬件架构(如 "x86_64")。
  • 这个函数通常用于系统诊断、脚本编写或获取系统特性等场景。

sysinfo() 函数

函数原型
int sysinfo(struct sysinfo *info);
参数
  • info:一个指向 struct sysinfo 结构的指针。这个结构体用于存储系统信息。
返回值
  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno
结构体 struct sysinfo
struct sysinfo {
    long uptime;            // 系统启动后经过的时间(以秒为单位)
    unsigned long loads[3]; // 1、5、15 分钟的平均负载
    unsigned long totalram; // 总物理内存(字节)
    unsigned long freeram;  // 空闲物理内存(字节)
    unsigned long sharedram; // 共享内存(字节)
    unsigned long bufferram; // 缓冲区内存(字节)
    unsigned long totalswap; // 总交换空间(字节)
    unsigned long freeswap;  // 空闲交换空间(字节)
    unsigned short procs;    // 正在运行的进程数
    unsigned long totalhigh; // 高内存(字节)
    unsigned long freehigh;  // 空闲高内存(字节)
    unsigned int mem_unit;   // 内存单位(通常是 1 字节)
    char _f[20];             // 保留字段(供将来使用)
};
说明
  • uptime:系统自上次启动以来的时间,单位为秒。
  • loads:系统负载的三个不同时间段(1 分钟、5 分钟、15 分钟)的平均值。这些值可以用来评估系统的负载情况。
  • totalramfreeramsharedrambufferram:关于物理内存的信息。totalram 是总内存,freeram 是未被使用的内存,sharedram 是共享内存(如 IPC),bufferram 是用于文件系统缓冲的内存。
  • totalswapfreeswap:交换空间的总量和未被使用的交换空间。
  • procs:当前运行的进程数量。
  • totalhighfreehigh:高内存的总量和未使用的高内存。这些字段在大多数现代系统上可能不使用。
  • mem_unit:内存单位的大小(通常是字节),用于表示其他内存相关字段的单位。

...

gethostname()

sysconf()

时间与日期

time() 函数

函数原型

time_t time(time_t *t);

参数

  • t:一个指向 time_t 类型的指针。如果不为 NULLtime() 会把当前时间存储在这个指针指向的变量中。

返回值

  • 返回当前时间,单位是自 1970 年 1 月 1 日 00:00:00 UTC(协调世界时)以来的秒数(称为 Unix 时间戳)。
  • 如果出错,返回 (time_t) -1

使用示例

  • #include <stdio.h>
    #include <time.h>
    
    int main() {
        time_t current_time;
        current_time = time(NULL); // 获取当前时间
        if (current_time != (time_t) -1) {
            printf("Current time: %s", ctime(&current_time)); // 转换为可读字符串
        } else {
            perror("time");
        }
        return 0;
    }
    

说明

  • time() 函数可以用来获取当前时间戳,也可以通过传递一个 time_t 类型的指针来获取时间戳并将其存储在该指针指向的变量中。
  • ctime() 函数用于将时间戳转换为可读的字符串格式。

gettimeofday() 函数

函数原型
int gettimeofday(struct timeval *tv, struct timezone *tz);
参数
  • tv:指向 struct timeval 结构体的指针,用于存储当前时间。
  • tz:指向 struct timezone 结构体的指针,用于存储时区信息。这个参数在现代 Linux 系统中通常不再使用,可以传递 NULL
返回值
  • 成功时返回 0。
  • 失败时返回 -1,并设置 errno
结构体 struct timeval
struct timeval {
    time_t      tv_sec;   // 从 1970 年 1 月 1 日起的秒数
    suseconds_t tv_usec;  // 微秒数
};
结构体 struct timezone(不再使用,通常传 NULL):
struct timezone {
    int tz_minuteswest;  // 当前时区与格林威治时间的差值(以分钟为单位)
    int tz_dsttime;      // 夏令时类型(通常不再使用)
};

时间转换

ctime()

localtime()

gmtime()

mktime()

asctime()

strftime()

用到再查

 进程时间

times()函数

函数介绍

times() 函数是 Linux 系统调用中用于获取进程时间的函数。它用于获取进程及其子进程所消耗的 CPU 时间。这个函数可以帮助我们了解进程的执行时间,并对进程性能进行分析。

函数原型
clock_t times(struct tms *buf);

参数

  • buf:指向 struct tms 结构体的指针,该结构体用于存储进程时间信息。

返回值

  • 返回当前进程的进程时间(以 clock_t 类型表示的秒数),如果出错则返回 (clock_t) -1

结构体 struct tms

struct tms {
    clock_t tms_utime;  // 用户态 CPU 时间
    clock_t tms_stime;  // 系统态 CPU 时间
    clock_t tms_cutime; // 所有子进程的用户态 CPU 时间
    clock_t tms_cstime; // 所有子进程的系统态 CPU 时间
};

说明

times() 函数用于获取进程的 CPU 时间统计信息。这些信息包括:

  • 用户态 CPU 时间 (tms_utime):进程在用户态(即非内核态)消耗的 CPU 时间。
  • 系统态 CPU 时间 (tms_stime):进程在内核态消耗的 CPU 时间。
  • 所有子进程的用户态 CPU 时间 (tms_cutime):子进程在用户态消耗的 CPU 时间总和。
  • 所有子进程的系统态 CPU 时间 (tms_cstime):子进程在内核态消耗的 CPU 时间总和。
  • sysconf(_SC_CLK_TCK) 用于获取系统的时钟滴答频率(每秒钟的时钟滴答数),通常是 100 或 1000。它用于将 clock_t 类型的值转换为秒数。
  • times() 可以用来计算进程的运行时间、性能分析等。

clock()函数

函数原型
clock_t clock(void);
返回值
  • 返回一个 clock_t 类型的值,表示从程序启动以来经过的 CPU 时间(以时钟滴答为单位)。如果出错,返回 (clock_t) -1
clock() 的工作原理

单位

  • clock_t 是一个表示 CPU 时间的类型,它的单位是时钟滴答。一个时钟滴答表示 CPU 的一个计时周期。时钟滴答的频率可以通过 CLOCKS_PER_SEC 常量来获取,该常量定义了每秒钟有多少个时钟滴答。
计算 CPU 时间

clock() 返回的时间是从程序开始运行到调用 clock() 函数时的 CPU 时间总和。这个时间包括了程序自身以及它调用的系统和库函数使用的 CPU 时间。它并不包括程序在等待 I/O 操作或其他阻塞操作时的时间。

常量 CLOCKS_PER_SEC

CLOCKS_PER_SEC 是一个常量,表示每秒钟的时钟滴答数。通常这个值是 1000000,表示每秒有一百万个时钟滴答(即微秒级别)。这个值可以用来将 clock_t 类型的值转换为秒数。

生成随机数

rand()函数

函数介绍

rand() 函数用于生成伪随机数。它在 C 标准库中定义,通常用于生成一系列看似随机的整数。

函数原型
#include <stdlib.h>
int rand(void);
返回值
  • 返回一个介于 0 和 RAND_MAX 之间的整数,RAND_MAX 通常是 32767,但可能因实现而异。
使用方法

初始化随机数生成器: 在调用 rand() 之前,通常用 srand() 函数设置随机数生成器的种子,这样每次运行程序时能得到不同的随机序列。种子的设置方式通常是使用当前时间:

#include <stdlib.h>
#include <time.h>

int main() {
    srand(time(NULL));  // 用当前时间设置种子
    int random_number = rand();  // 生成随机数
    return 0;
}

生成随机数

int random_number = rand();

生成指定范围的随机数: 可以通过取模运算和加法来限制随机数的范围。例如,生成 0 到 99 之间的随机数:

int random_number = rand() % 100;
注意事项
  • 伪随机性rand() 生成的数是伪随机的,基于算法生成的,并不是完全随机的。用相同的种子每次生成的数列都是一样的。

  • 种子设置:如果不调用 srand()rand() 每次运行时生成的序列都相同,通常会在调试时设置一个固定的种子。

  • 线程安全:在多线程环境中,rand() 不是线程安全的,建议使用 rand_r() 函数或其他线程安全的随机数生成函数。

srand()函数(设置随机数种子)

函数介绍

srand() 函数用于初始化随机数生成器的种子。在使用 rand() 函数生成随机数之前,调用 srand() 来设置种子可以确保每次运行程序时生成不同的随机序列。

函数原型
#include <stdlib.h>
void srand(unsigned int seed);
参数
  • seed:种子值,类型为 unsigned int。这个值用来初始化随机数生成器的状态。
作用
  • 初始化随机数生成器srand() 用指定的种子值初始化随机数生成器。这个过程设定了随机数生成器的初始状态,影响随后调用 rand() 函数时生成的随机数序列。
使用方法

设置种子: 在生成随机数之前调用 srand(),通常用系统时间作为种子,使得每次运行程序时生成不同的随机数序列。例如:

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
    srand(time(NULL));  // 用当前时间作为种子
    int random_number = rand();  // 生成随机数
    printf("Random number: %d\n", random_number);
    return 0;
}

在这个示例中,time(NULL) 返回当前时间的秒数作为种子,这样每次程序运行时,种子值都不同,从而生成不同的随机数序列。

生成随机数: 通过调用 rand() 生成随机数:

int random_number = rand();
细节和注意事项

随机序列的确定性: 如果你使用相同的种子调用 srand(),之后调用 rand() 会生成相同的随机数序列。例如:

srand(1234);
int num1 = rand();  // 生成的随机数是确定的
int num2 = rand();  // 生成的随机数也是确定的

如果你再次用相同的种子(例如 1234),会得到相同的 num1num2

避免种子冲突: 使用系统时间(如 time(NULL))作为种子是常见做法,因为它在每次运行时都不同,从而避免了种子冲突,确保了每次程序运行时生成不同的随机数序列。

线程安全srand()rand() 都不是线程安全的。在多线程程序中,最好使用线程安全的随机数生成函数,如 rand_r() 或 POSIX 的 random()

休眠

sleep()函数

函数原型
#include <unistd.h>
unsigned int sleep(unsigned int seconds);
参数
  • seconds:暂停的时间,单位是秒。传递给 sleep() 函数的参数是你希望程序暂停的时间长度。
作用
  • 暂停执行sleep() 会让程序暂停执行指定的秒数。在这段时间内,程序不会执行任何操作或消耗 CPU 时间。

usleep()函数(微秒级别)

函数原型
#include <unistd.h>
int usleep(useconds_t usec);
参数
  • usec:暂停时间,单位是微秒(1 微秒 = 1/1,000,000 秒)。这个参数指定了程序应当暂停的时间长度。
作用
  • 精确暂停usleep() 用于使程序暂停精确的微秒数。这对于需要短暂且高精度延迟的应用场景很有用,比如定时任务或高频率的数据采集。

nanosleep()函数

函数原型
#include <time.h>
int nanosleep(const struct timespec *req, struct timespec *rem);
参数

req:一个 timespec 结构体指针,指定暂停的时间。timespec 结构体包含两个字段:

tv_sec:暂停的秒数。

tv_nsec:暂停的纳秒数(0 到 999,999,999)。

rem:一个 timespec 结构体指针,用于存储如果暂停被信号中断时剩余的时间。如果不需要此功能,可以传递 NULL

作用

nanosleep() 函数使程序暂停指定的时间长度,提供比 sleep()usleep() 更高的精度。

返回值
  • 成功: 返回 0,表示成功暂停了指定时间。

  • 失败: 返回 -1,并设置 errno。常见的错误包括:

    • EINTR:暂停被信号中断。rem 结构体将包含剩余时间。
    • EINVAL:提供的时间参数无效。
错误处理

如果暂停被信号中断,nanosleep() 会返回 -1,并在 rem 中填充剩余时间。可以根据 errno 的值来确定失败的具体原因。

申请内存

malloc()

free()

信号

信号概念

信号是发生事件时对进程的一种通知机制。信号的目的是用来通信。信号的发送者既可以是进程也可以是内核。信号由进程接收,处理。

信号是异步的。

信号的本质是int类型的数字编号。

进程常见处理(标准)信号的方式:

忽略信号,捕获信号,执行信号的默认处理方式

signal()函数
函数介绍

signal() 函数用于设定处理特定信号的函数。当程序收到一个信号时,signal() 可以用来定义如何处理该信号。

函数原型
#include <signal.h>
void (*signal(int signum, void (*handler)(int)))(int);
参数
  • signum:要处理的信号编号,如 SIGINTSIGTERMSIGKILL 等。
  • handler:指向处理函数的指针,这个函数在收到信号时被调用。处理函数的原型是 void handler(int signum)
返回值
  • 成功:返回上一个处理程序的指针。
  • 失败:返回 SIG_ERR,表示出错。可以通过 errno 获取错误信息。

sigaction()函数
函数介绍

sigaction() 函数用于更精细地控制信号的处理。相比于较简单的 signal() 函数,sigaction() 提供了更多的功能和灵活性来处理信号。

函数原型
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
参数
  • signum:要处理的信号编号,如 SIGINTSIGTERMSIGUSR1 等。
  • act:指向 sigaction 结构体的指针,该结构体定义了新的信号处理方式。
  • oldact:指向 sigaction 结构体的指针,用于存储旧的信号处理方式。如果不需要保存旧的处理方式,可以传递 NULL

sigaction 结构体
#include <signal.h>
struct sigaction {
    void (*sa_handler)(int);          // 信号处理函数的指针
    void (*sa_sigaction)(int, siginfo_t *, void *); // 信号处理函数的指针(更复杂的版本)
    sigset_t sa_mask;                 // 在处理信号时要阻塞的信号集
    int sa_flags;                     // 信号处理选项
};
sa_flags 标志位
  • SA_RESTART:在处理信号时,如果系统调用被中断,可以自动重新启动系统调用。
  • SA_NOCLDSTOP:忽略子进程停止的信号(如 SIGCHLD)。
  • SA_SIGINFO:使用 sa_sigaction 处理函数,而不是 sa_handler
  • SA_IGNORE:忽略信号。
  • SA_HANDLER:使用 sa_handler 指定的函数处理信号。

信号的分类

(实时信号)不可靠信号(1-31):Linux下的不可靠信号的不可靠问题主要指的是信号可能会丢失

(非实时信号,标准信号)可靠信号(34-64):可靠信号支持排队,不会丢失。

向进程发送信号

kill()
函数介绍

kill() 函数用于向指定的进程发送信号。虽然函数名是 kill,但它不仅仅用于终止进程,还可以用来发送其他类型的信号,比如暂停、继续或重新加载配置。

函数原型
int kill(pid_t pid, int sig);
  • pid:目标进程的进程 ID。如果是负数,表示向进程组发送信号;如果是 0,表示向当前进程组中的所有进程发送信号。
  • sig:要发送的信号编号。例如,SIGKILL(强制终止)、SIGTERM(请求终止)等。

raise()

函数介绍

raise() 函数用于向当前进程发送信号。这是一个简单的方式来向自己发送信号,用于处理进程内部的信号。

函数原型
int raise(int sig);
  • sig:要发送的信号编号。例如,SIGINT(中断)、SIGUSR1(用户定义信号 1)等。

进程通讯

共享内存

shm_open()

函数介绍

shm_open 是用于创建或打开一个 POSIX 共享内存对象的函数。共享内存允许不同的进程访问相同的内存区域,常用于进程间通信(IPC)。

函数原型
int shm_open(const char *name, int oflag, mode_t mode);

name: 共享内存对象的名字,应该以 / 开头。例如,/my_shm

oflag: 打开的标志,包括:

  • O_CREAT: 如果共享内存对象不存在,则创建它。
  • O_EXCL: 如果与 O_CREAT 一起使用且对象已存在,则返回错误。
  • O_RDONLY: 以只读模式打开。
  • O_RDWR: 以读写模式打开。

mode: 对新创建共享内存对象的权限设置,通常是一个八进制数。例如,0666 允许所有用户读写。

返回值

成功时返回一个文件描述符,失败时返回 -1 并设置 errno

示例
int shm_fd = shm_open("/my_shm", O_CREAT | O_RDWR, 0666);
if (shm_fd == -1) {
    perror("shm_open");
    exit(EXIT_FAILURE);
}

mmap()

函数介绍

mmap 是用于将文件或设备映射到内存的函数,也可以用于将共享内存对象映射到进程的地址空间。通过 mmap,进程可以直接在内存中读写数据,而不是使用读写系统调用。

原型
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

addr: 期望的映射起始地址,通常设为 NULL 让系统自动选择。

length: 映射区域的大小(字节)。

prot: 映射区域的保护标志,例如:

  • PROT_READ: 允许读取。
  • PROT_WRITE: 允许写入。
  • PROT_EXEC: 允许执行。

flags: 映射的选项,例如:

  • MAP_SHARED: 映射区域的更改会被写回到文件或共享内存对象。
  • MAP_PRIVATE: 映射区域的更改不会影响文件或共享内存对象。
  • fd: 共享内存对象的文件描述符(由 shm_open 返回)。
  • offset: 映射区域在文件中的起始位置,通常为 0
返回值
  • 成功时返回映射区域的指针,失败时返回 MAP_FAILED

munmap()

函数介绍

munmap 是用于解除之前通过 mmap 映射的内存区域的函数。调用 munmap 可以释放由 mmap 分配的内存映射。

原型
int munmap(void *addr, size_t length);
  • addr: 映射区域的起始地址。
  • length: 映射区域的大小(字节)。
返回值
  • 成功时返回 0,失败时返回 -1 并设置 errno

shm_unlink()

函数介绍

shm_unlink 是用于删除一个 POSIX 共享内存对象的函数。它会删除指定的共享内存对象名称,使得后续的 shm_open 调用将无法再访问这个对象。

原型
int shm_unlink(const char *name);
  • name: 要删除的共享内存对象的名字,应该以 / 开头。
返回值
  • 成功时返回 0,失败时返回 -1 并设置 errno

消息队列

mq_open

函数原型:
mqd_t mq_open(const char *name, int oflag, mode_t mode, const struct mq_attr *attr);
参数解析:

name: 消息队列的名称,必须以 / 开头,例如 /my_queue

oflag: 打开标志,组合使用。常用的标志有:

  • O_CREAT: 如果消息队列不存在,则创建它。
  • O_RDONLY: 以只读模式打开消息队列。
  • O_WRONLY: 以只写模式打开消息队列。
  • O_RDWR: 以读写模式打开消息队列。

mode: 权限设置,如 0666,表示所有用户都有读写权限。此参数只有在 O_CREAT 标志被使用时有效。

attr: 指向 mq_attr 结构体的指针,用于设置消息队列的属性(例如消息最大长度和队列最大容量)。如果不需要特殊属性,可以传 NULL

返回值:
  • 成功时返回一个消息队列描述符(mqd_t 类型)。
  • 失败时返回 -1 并设置 errno

示例代码:
#include <fcntl.h>
#include <sys/stat.h>
#include <mqueue.h>

// 创建或打开名为 "/my_queue" 的消息队列
mqd_t mq = mq_open("/my_queue", O_CREAT | O_RDWR, 0666, NULL);
// 如果 mq_open 失败,返回 -1
if (mq == (mqd_t)-1) {
    perror("mq_open failed");
}

此示例创建了一个名为 /my_queue 的消息队列,若已存在则以读写模式打开它。

mq_timedreceive

函数原型:
ssize_t mq_timedreceive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio, const struct timespec *timeout);

参数解析:

mqdes: 消息队列的描述符,由 mq_open 返回。

msg_ptr: 指向接收消息的缓冲区。

msg_len: 缓冲区的大小,必须足够容纳接收到的消息。

msg_prio: 指向存储消息优先级的变量的指针。可以传 NULL 如果不需要优先级。

timeout: 指向 timespec 结构体的指针,设置超时时间。

返回值:
  • 成功时返回接收到的消息长度。
  • 失败时返回 -1 并设置 errno

示例代码:
#include <time.h>
#include <mqueue.h>
#include <stdio.h>

struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 5;  // 设置5秒超时

char buffer[256];
ssize_t bytes_received = mq_timedreceive(mq, buffer, sizeof(buffer), NULL, &ts);

// 如果 mq_timedreceive 失败
if (bytes_received == -1) {
    perror("mq_timedreceive failed");
} else {
    printf("Received message: %s\n", buffer);
}

此示例从消息队列接收消息,最多等待5秒。如果5秒内未收到消息,函数会返回超时错误。

mq_timedsend

函数原型:
int mq_timedsend(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio, const struct timespec *timeout);

参数解析:

mqdes: 消息队列的描述符,由 mq_open 返回。

msg_ptr: 指向要发送的消息数据。

msg_len: 消息的长度。

msg_prio: 消息的优先级。

timeout: 指向 timespec 结构体的指针,设置超时时间。

返回值:
  • 成功时返回 0
  • 失败时返回 -1 并设置 errno
示例代码:
#include <time.h>
#include <mqueue.h>
#include <stdio.h>

struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 5;  // 设置5秒超时

const char *msg = "Hello, World!";
int ret = mq_timedsend(mq, msg, strlen(msg) + 1, 1, &ts);

// 如果 mq_timedsend 失败
if (ret == -1) {
    perror("mq_timedsend failed");
}

此示例向消息队列发送消息 "Hello, World!",最多等待5秒。如果在5秒内无法完成发送,函数会返回超时错误。

clock_gettime

函数原型:
int clock_gettime(clockid_t clk_id, struct timespec *tp);

参数解析:

clk_id: 时钟标识符。常见的标识符有:

  • CLOCK_REALTIME: 实时时钟。
  • CLOCK_MONOTONIC: 单调时钟。

tp: 指向 timespec 结构体的指针,用于存储获取的时间值。

返回值:
  • 成功时返回 0
  • 失败时返回 -1 并设置 errno

示例代码:

#include <time.h>
#include <stdio.h>

struct timespec ts;
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
    perror("clock_gettime failed");
} else {
    printf("Current time: %ld.%ld\n", ts.tv_sec, ts.tv_nsec);
}

此示例获取当前实时时间并打印出来。

mq_unlink

函数原型:
int mq_unlink(const char *name);

参数解析:
  • name: 要删除的消息队列的名称。
返回值:
  • 成功时返回 0
  • 失败时返回 -1 并设置 errno

示例代码:

#include <mqueue.h>
#include <stdio.h>

if (mq_unlink("/my_queue") == -1) {
    perror("mq_unlink failed");
}

此示例删除名为 /my_queue 的消息队列。消息队列只有在所有打开的描述符都被关闭后才会实际删除。

线程处理

概念介绍

简单理解:线程的概念较小。一个进程里可以有多个线程。

线程之间的通信比进程间通信方便。

除了每个线程都有独立的栈空间,同一进程的多线程共享大部分资源。

线程创建的相关函数

pthread_create()

函数介绍

pthread_create() 用于创建一个新的线程,并启动线程执行指定的函数。线程是操作系统中能够独立执行的最小单位,允许多个线程并发地执行任务,从而提高程序的效率和响应能力。

函数原型
#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void *), void *arg);
参数解析
  • pthread_t *thread: 这是一个指向 pthread_t 类型的指针,用于存储新创建的线程的标识符。pthread_t 是一个线程句柄,通常由系统内部管理,你不需要了解其具体实现细节。

  • const pthread_attr_t *attr: 这是一个指向线程属性对象的指针。可以用来指定线程的属性(如栈大小、调度策略等)。如果设置为 NULL,则使用默认属性。

  • void *(*start_routine)(void *): 这是一个函数指针,指向线程执行的函数。这个函数必须接受一个 void* 类型的参数,并返回一个 void* 类型的结果。

  • void *arg: 这是传递给 start_routine 函数的参数。由于 start_routine 函数的参数是 void* 类型,因此你可以将任意类型的指针传递给它。这个参数在调用线程时会传递给 start_routine

返回值

0: 表示线程创建成功。

非0: 表示线程创建失败。具体的错误码可以参考 errno.h 中的定义,如 ENOMEM(内存不足)或 EAGAIN(系统资源不足)。

线程终止

线程终止的方法:

  1. 线程函数执行return语句
  2. 线程函数内部调用pthread_exit()函数
  3. 其他线程调用pthread_cancel函数

线程终止的相关函数

pthread_cancel()
函数介绍

pthread_cancel() 函数用于请求取消一个线程。它向目标线程发送取消请求,目标线程在达到取消点时会响应这个请求。

函数原型
#include <pthread.h>

int pthread_cancel(pthread_t thread);
参数解析
  • pthread_t thread: 目标线程的线程标识符。调用此函数的线程可以向指定的线程发送取消请求。
返回值
  • 成功: 返回 0
  • 失败: 返回错误码,具体值取决于出错的原因。

pthread_setcancelstate()
函数介绍

pthread_setcancelstate() 函数用于设置线程的取消状态。线程可以设置为响应取消请求或者忽略取消请求。

函数原型
#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);
参数解析

int state: 要设置的取消状态。可以是以下两个值之一:

  • PTHREAD_CANCEL_ENABLE: 线程响应取消请求(默认值)。
  • PTHREAD_CANCEL_DISABLE: 线程忽略取消请求。

int *oldstate: 用于存储线程之前的取消状态。如果不需要,可以传递 NULL

pthread_setcanceltype()
函数介绍

pthread_setcanceltype() 函数用于设置线程取消的类型。这影响到线程在接收到取消请求时的行为。

函数原型
#include <pthread.h>

int pthread_setcanceltype(int type, int *oldtype);
参数解析
  • int type: 取消类型。可以是以下两个值之一:
    • PTHREAD_CANCEL_DEFERRED: 取消请求会被延迟到取消点(默认值)。
    • PTHREAD_CANCEL_ASYNCHRONOUS: 取消请求会立即生效,不管线程的执行状态。
  • int *oldtype: 用于存储线程之前的取消类型。如果不需要,可以传递 NULL
返回值
  • 成功: 返回 0
  • 失败: 返回错误码,具体值取决于出错的原因。

pthread_detach()
函数介绍

pthread_detach() 函数用于分离线程。分离的线程在结束时会自动释放其占用的资源,不需要调用 pthread_join() 来回收资源。

函数原型
#include <pthread.h>

int pthread_detach(pthread_t thread);
参数解析
  • pthread_t thread: 需要分离的线程的线程标识符。
返回值
  • 成功: 返回 0
  • 失败: 返回错误码,具体值取决于出错的原因。

pthread_join()
函数介绍

pthread_join() 是一个用于等待线程结束的函数,它在多线程编程中非常常用。

函数原型
#include <pthread.h>

int pthread_join(pthread_t thread, void **retval);
参数解析
  • pthread_t thread: 目标线程的线程标识符。调用此函数的线程将会等待此线程的结束。
  • void **retval: 用于存储线程的返回值的指针。如果不需要线程的返回值,可以传递 NULL
返回值

成功: 返回 0

失败: 返回错误码,具体值取决于出错的原因。常见的错误码包括:

  • ESRCH: 指定的线程不存在。
  • EINVAL: 线程不是一个可合并线程或已经被取消。
  • EDEADLK: 发生死锁(例如,线程试图等待自己)。
功能

pthread_join() 函数的主要作用是:

  1. 等待线程结束: 调用线程会阻塞,直到目标线程执行完毕。
  2. 获取线程的返回值: 如果目标线程有返回值,pthread_join() 可以通过 retval 参数获取该值。
  3. 清理线程资源: 在目标线程结束后,pthread_join() 会释放与线程相关的资源(如果线程是附加的)。

线程同步

互斥锁

读写锁

读操作:在读写锁的控制下,多个线程可以同时获得读锁。这些线程可以并发地读取
共享资源,但它们的存在阻止了写锁的授予。

写操作:如果至少有一个读操作持有读锁,写操作就无法获得写锁。写操作将会阻塞,
直到所有的读锁都被释放。

熟悉相关API的调用。

运行顺序:写锁只能单个线程使用,读锁可以多个线程一起使用。

当读操作过于频繁以至于妨碍了写操作则会导致读饥饿。可以利用特定函数改为写优先即可解决。

自选锁(了解即可)

信号量

二进制信号量
计数信号量

有名信号量

无名信号量

P操作:也称为等待操作(wait),用于减少信号量的值。

V操作:也称为信号操作(signal),用于增加信号量的值。

内核原理

进程控制块(PCB)

保存着进程编号pid。

进程状态信息。

保存着进程切换需要保存和恢复的一些cpu寄存器

....

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值