本文主要是介绍嵌入式Linux C应用编程指南-系统资源与信号(速记版),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
第七章 系统信息
7.1 系统信息
7.1.1 系统标识 uname
系统调用 uname()用于获取有关当前操作系统内核的名称和信息。
#include <sys/utsname.h>/* 获取当前内核系统信息 */
int uname(struct utsname *buf);
struct utsname {char sysname[]; /* 当前操作系统的名称 */char nodename[]; /* 网络上的名称(主机名) */char release[]; /* 操作系统内核版本 */char version[]; /* 操作系统发行版本 */char machine[]; /* 硬件架构类型 */#ifdef _GNU_SOURCEchar domainname[];/* 当前域名 */#endif
};
7.1.2 sysinfo 函数
sysinfo 系统调用可用于获取一些系统统计信息。包括系统启动经过多久时间(秒)、进程数量、总内存大小、可用内存大小等。
#include <sys/sysinfo.h>int sysinfo(struct sysinfo *info);
struct sysinfo {long uptime; /* 自系统启动之后所经过的时间(以秒为单位) */unsigned long loads[3]; /* 1, 5, and 15 minute load averages */unsigned long totalram; /* 总的可用内存大小 */unsigned long freeram; /* 还未被使用的内存大小 */unsigned long sharedram; /* Amount of shared memory */unsigned long bufferram; /* Memory used by buffers */unsigned long totalswap; /* Total swap space size */unsigned long freeswap; /* swap space still available */unsigned short procs; /* 系统当前进程数量 */unsigned long totalhigh; /* Total high memory size */unsigned long freehigh; /* Available high memory size */unsigned int mem_unit; /* 内存单元大小(以字节为单位) */char _f[20-2*sizeof(long)-sizeof(int)]; /* Padding to 64 bytes */
};
7.1.3 gethostname 函数
函数 gethostname() 可用于单独获取 Linux 系统主机名,与 struct utsname 数据结构体中的 nodename 变量一样。
#include <unistd.h>
int gethostname(char *name, //结果缓冲区size_t len); //缓冲区长度
7.1.4 sysconf()函数
库函数 sysconf(),可在获取系统的运行时配置信息,譬如页大小(page size)、主机名的最大长度、进程可以打开的最大文件数、每个用户 ID 的最大并发进程数等。
#include <unistd.h>
/* 获取系统的运行时配置信息 */
long sysconf(int name);//name指定要获取什么信息/*
_SC_HOST_NAME_MAX:主机名的最大长度。
_SC_LOGIN_NAME_MAX:登录名的最大长度。_SC_PAGESIZE:系统页大小(page size)。
_SC_CHILD_MAX:每个用户的最大并发进程数。
*/
7.2 时间、日期
7.2.1 时间的概念
GMT 时间
英国格林威治标准时间。我国的标准时间北京时间(东八区)早8个小时。
UTC 时间
世界标准时间。是修正后的GMT时间。date -u 可查看当前UTC时间。
时区
全球被划分为 24 个时区,每一个时区横跨经度 15 度,以英国格林威治的本初子午线作为零度经线。东十二区和西十二区其实是一个时区,就是十二区。date 可查看系统本地时间。
时区信息通常以标准格式保存在/usr/share/zoneinfo目录下,该目录下的每一个文件都包含了一个国家时区制度的相关信息。也把这些文件称为时区配置文件。
系统的本地时间由时区配置文件/etc/localtime 定义,通常链接到/usr/share/zoneinfo 目录下。
要修改本地时区信息,可以直接将/etc/localtime 链接到/usr/share/zoneinfo 目录下的任意一个时区配置文件。
sudo rm -rf localtime #删除原有链接文件
sudo ln -s /usr/share/zoneinfo/EST localtime #重新建立链接文件
7.2.2 Linux 系统中的时间
点时间和段时间
点时间顾名思义指的是某一个时间点。段时间来说,顾名思义指的是某一个时间段。
实时时钟 RTC
操作系统中一般会有两个时钟,一个系统时钟,一个实时时钟(RTC)。
系统时钟由内核维护,使用 date 命令查看到的就是系统时钟;而 RTC 时钟一般由 RTC 时钟芯片提供,由后备电池供电。
Linux 系统如何记录时间
Linux 系统在开机启动之后会读取 RTC 实时时钟作为系统时钟的初始值,之后内核便开始维护系统时钟。
jiffies 的引入
jiffies 是内核中定义的一个全局变量,用来记录系统从启动以来的系统节拍数。内核在编译配置时定义了一个节拍时间,使用节拍率来表示。
高节拍率会导致系统中断的产生更加频繁,频繁的中断会加剧系统的负担,默认采用 100Hz 作为系统节拍率。
内核通过系统节拍数 jiffies 来维护系统时钟。全局变量 jiffies 在系统开机启动时会读取RTC,给 jiffies 设置一个初始值。系统时钟初始化指的就是读取 RTC 对内核 jiffies 变量进行初始化。
系统调用 time()、gettimeofday(),其实质上就是通过 jiffies 变量换算得到。
7.2.3 获取时间 time/gettimeofday
(1) time 函数
系统调用 time()用于获取当前时间,以秒为单位,返回得到的值是自 1970-01-01 00:00:00 +0000 (UTC) 以来的秒数。通常将 time_t类型成为日历时间。
#include <time.h>
/*通过系统节拍 jiffeis 获得系统时间*/
time_t time(time_t *tloc);//如果 不为NULL,则时间也记录在参数中
(2) gettimeofday 函数
系统调用 gettimeofday()函数提供微秒级时间精度。
#include <sys/time.h>/*获取1970-1-1 0:0:0 以来的日历时间(秒数),精确到微妙*/
int gettimeofday(struct timeval *tv, //timeval结构的成员就是秒、微妙struct timezone *tz);//timezone已废弃,给NULL
7.2.4 时间转换函数
日历时间转字符串:
ctime、ctime_r //将time_t 转 本地时间字符串,星期/月/日 时分秒/年
日历时间转 struct tm 结构体:
localtime //将 日历时间 转 分解时间 struct tm 结构体,本地时间
gmtime //将 日历时间 转 分解时间 struct tm 结构体,标准时间
将 struct tm 结构体分解时间 转日历时间 time_t:
mktime //将 分解时间 struct tm 转 日历时间 time_t
struct tm 分解时间转字符串:
asctime //将分解时间转固定字符串。星期/月/日 时分秒/年
strftime //将分解时间转自定义字符串。
(1) ctime 和 ctime_r函数
通过 time()或 gettimeofday()函数可以获取到日历时间,但秒和微秒不利于阅读。
可使用C库函数 ctime() 将日历时间转换为字符串。但是ctime()是一个不可重入函数,依赖于全局变量,容易在多线程环境下出问题。
ctime_r() 是 ctime()的可重入版本。
#include <time.h>/*将 日历时间time_t转换为本地时间字符串*/
char *ctime(const time_t *timep);/*将 日历时间time_t转换为本地时间字符串,可重入 */
char *ctime_r(const time_t *timep,//需要转换的时间char *buf); //缓冲区首地址
(2) localtime 函数
localtime() 函数可以把 time()或 gettimeofday()得到的秒数(日历时间)变成一个 struct tm 结构体所表示的时间,该时间对应的是本地时间。
#include <time.h>/* 将日历时间time_t 转换为 struct tm 结构体,对应本地时间 */
struct tm *localtime(const time_t *timep);/* 将日历时间time_t 转换为 struct tm 结构体,对应本地时间 可重入*/
struct tm *localtime_r(const time_t *timep, struct tm *result);
struct tm {int tm_sec; /* 秒(0-60) */int tm_min; /* 分(0-59) */int tm_hour; /* 时(0-23) */int tm_mday; /* 日(1-31) */int tm_mon; /* 月(0-11) */int tm_year; /* 年(这个值表示的是自 1900 年到现在经过的年数) */int tm_wday; /* 星期(0-6, 星期日 Sunday = 0、星期一=1…) */int tm_yday; /* 一年里的第几天(0-365, 1 Jan = 0) */int tm_isdst; /* 夏令时 */
};
(3) gmtime 函数
gmtime()函数也可以把 time_t 时间变成一个 struct tm 结构体所表示的时间,与 localtime()所不同的是, gmtime()函数所得到的是 UTC 国际标准时间,并不是计算机的本地时间。
#include <time.h>/*将 time_t 日历时间转变成 UTC标准时间*/
struct tm *gmtime(const time_t *timep);/*将 time_t 日历时间转变成 UTC标准时间,可重入版本*/
struct tm *gmtime_r(const time_t *timep, struct tm *result);
(4) mktime 函数
mktime()可以将使用 struct tm 结构体表示的分解时间转换为 time_t 日历时间。
#include <time.h>
/* 将 struct tm 时间转换为 日历时间time_t */
time_t mktime(struct tm *tm);
(5) asctime 函数
ctime()是将 time_t 时间转换为固定格式字符串。
asctime()则是将 struct tm 表示的分解时间转换为固定格式的字符串。星期月天时分秒年
#include <time.h>/* 将 分解时间struct tm 转换为 固定格式的字符串 */
char *asctime(const struct tm *tm);char *asctime_r(const struct tm *tm, char *buf);//可重入
(6) strftime 函数
将一个 struct tm 变 量表示的分解时间转换为为格式化字符串,允许自定义格式。
#include <time.h>size_t strftime(char *s, //缓存区指针,存放生成的字符串size_t max, //字符串的最大字节数const char *format,//字符和占位符const struct tm *tm);//分解时间 struct tm
7.2.5 设置时间 settimeofday
使用 settimeofday()函数可以用 timeval 结构体变量设置系统本地时间。
#include <sys/time.h>int settimeofday(const struct timeval *tv, //timeval时间结构体const struct timezone *tz);//已废弃,给NULL
7.3 进程时间
进程时间指的是进程从程序运行后到目前为止,这段时间内使用 CPU 资源的时间总数。
内核把 CPU 时间(进程时间)分为用户进程时间 和 系统进程时间。用户进程时间就是进程用户态耗费的CPU时间,系统进程时间就是内核态耗费的CPU时间。
进程时间指的是用户 CPU 时间和系统 CPU 时间的总和。
7.3.1 times 函数
times() 函数用于获取当前进程时间。 time是获取日历时间。
#include <sys/times.h>/*获取当前进程时间*/
clock_t times(struct tms *buf);//进程时间存储在 tms 结构体
//返回值类型为 clock_t(实质是 long 类型),调用成功将返回系统节拍数。
struct tms {clock_t tms_utime; /* user time, 进程的用户 CPU 时间, tms_utime 个系统节拍数 */clock_t tms_stime; /* system time, 进程的系统 CPU 时间, tms_stime 个系统节拍数 */clock_t tms_cutime; /* user time of children, 已死掉子进程的+当前 用户CPU 时间总和 */clock_t tms_cstime; /* system time of children, 已死掉子进程的+当前 系统CPU 时间总和 */
};
查看程序运行到某一个位置时的进程时间,或者计算出程序中的某一段代码执行过程所花费的进程时间,都可以使用 times() 实现。
/* 获取系统的节拍率 */
tck = sysconf(_SC_CLK_TCK);/* 开始时间 */
t_start = times(&t_buf_start);/* 结束时间 */t_end = times(&t_buf_end);/* 打印时间 */printf("时间总和: %f 秒\n", (t_end - t_start) / (double)tck);printf("用户 CPU 时间: %f 秒\n", (t_buf_end.tms_utime - t_buf_start.tms_utime) / (double)tck);printf("系统 CPU 时间: %f 秒\n", (t_buf_end.tms_stime - t_buf_start.tms_stime) / (double)tck);
时间总和包括了进程处于休眠状态(sleep)时消耗的时间。
7.3.2 clock 函数
库函数 clock() 的返回值描述了进程使用的总的 CPU 时间(节拍数)。
#include <time.h>
/*返回值是进程CPU时间*/
clock_t clock(void);
7.4 产生随机数
随机数与伪随机数
编程能得到的都是伪随机,最常用 rand() 产生伪随机数,用 srand()产生随机种子。如果种子相同,那么伪随机数每次运行程序产生的序列都是相同的。
#include <stdlib.h>//返回随机[0, RAND_MAX]。要确定范围就用 rand()%100 + xxx
int rand(void);//产生随机种子。一般用当前时间time(null)
void srand(unsigned int seed);
7.5 休眠
进入休眠状态之后,程序将暂停运行。
常用的系统调用和 C 库函数有 sleep()、usleep()以及 nanosleep(),也被用作延时。
7.5.1 秒级休眠:sleep
sleep()是一个 C 库函数,用于线程休眠,秒级单位。
#include <unistd.h>//休眠。若被信号中断则返回剩余的秒数。
unsigned int sleep(unsigned int seconds);
7.5.2 微秒级休眠: usleep
usleep()是一个 C 库函数,用于线程休眠,微秒级单位。
#include <unistd.h>
//休眠。微秒单位。成功返回0失败返回-1。
int usleep(useconds_t usec);
7.5.3 纳秒级休眠: nanosleep
nanosleep()是一个系统调用,用于线程休眠,纳秒级单位。
#include <time.h>//休眠,纳秒精度,系统调用
int nanosleep(const struct timespec *req,//设置休眠时间。秒、纳秒struct timespec *rem); //NULL
struct timespec
{time_t tv_sec; /* 秒 */syscall_slong_t tv_nsec; /* 纳秒 */
}
7.6 申请堆内存
在堆上分配内存:malloc 和 free。
#include <stdlib.h>/*向堆上申请内存。申请内存失败返回NULL*/
void *malloc(size_t size);/*释放堆上空间*/
void free(void *ptr);
callloc()。申请n个连续的size大小的空间,并初始化为0。
#include<stdlib.h>/*堆上申请 多个连续的 size大小的空间,并初始化为0*/
void *calloc(size_t nmemb,//元素个数size_t size);//单个元素大小
malloc、calloc分配的内存其实是对齐的,但是对齐字节固定,而且对齐字节的边界较小。
分配对齐内存
标准库函数 posix_memalign()、aligned_alloc()用于在堆上分配对齐内存的函数。
#include <stdlib.h>int posix_memalign(void **memptr,//分配的空间地址。会是对齐字节数的整数倍size_t alignment,//对齐字节数。必须是2的幂次方。size_t size);//空间大小/*申请特定对齐字节的空间。返回地址指针。*/
void *aligned_alloc(size_t alignment,//对齐字节数size_t size); //申请空间的大小
7.7 proc 文件系统
proc 文件系统是一个虚拟文件系统,它以文件系统的方式为应用层访问系统内核数据提供了接口,用户和应用程序可以通过读写 proc 文件系统得到系统信息和进程信息。
与普通文件不同的是,proc 文件系统是动态创建的,不存在于磁盘、只存在于内存中,与 devfs 文件系统 一样,都被称为虚拟文件系统。
proc 文件系统挂载在系统的 /proc 目录下。
devfs 文件系统的设备文件一般存放在 /dev/ 目录下。
内核将运行时的一些关键数据信息以文件的方式呈现在 proc 文件系统下的特定文件中,这相当于将内核中的数据结构以可视化的方式呈现给应用层。
proc 文件系统挂载在系统的/proc 目录下。
有一种内核调试的基本方法:
通过看读取 /proc 文件来获取到内核特定数据结构的值,与添加了新功能前后对比,就可以判断此功能所产生的影响。
/proc 目录下中包含了一些目录和虚拟文件。
/proc 目录下使用进程 PID 号 命名文件夹,每个进程在内核中有唯一的 PID号。
这些文件夹中记录了进程的相关信息,不同的信息通过不同的虚拟文件呈现。
/proc 目录下除了文件夹之外,还有很多的虚拟文件,比如 buddyinfo、cgroups、cmdline、version 等等,记录了 内核 和 进程的相关信息。
cmdline:内核启动参数;
cpuinfo:CPU 相关信息;
iomem:IO 设备的内存使用情况;
interrupts:显示被占用的中断号和占用者相关的信息;
ioports:IO 端口的使用情况;
kcore:系统物理内存映像,不可读取;
loadavg:系统平均负载;
meminfo:物理内存和交换分区使用情况;
modules:加载的模块列表;
mounts:挂载的文件系统列表;
partitions:系统识别的分区表;
swaps:交换分区的利用情况;
version:内核版本信息;
uptime:系统运行时间;
7.7.1 proc 文件系统的使用
proc 文件系统的使用就是去读取/proc 目录下的这些文件,获取文件中记录的信息,可以直接使用 cat 命令读取,也先 open()打开、再 read()读取。
第八章 信号
8.1 基本概念
信号是事件发生时对进程的通知机制,也称为软件中断。
信号的目的是用来通信的
一个具有合适权限的进程能够向另一个进程发送信号,这是一种同步技术,也是IPC(进程间通信)的原始形式。
多种情况可以产生信号:
硬件发生异常。硬件检测错误并通知内核,内核发送信号给相关进程。
终端下输入了能够产生信号的特殊字符。CTRL+C产生中断信号,CTRL+Z产生暂停信号。
系统调用 kill()可将任意信号发送给另一个进程或进程组。
软件触发。入定时器、进程执行时间超限、子进程退出等。
信号由谁处理、怎么处理
信号通常是发送给对应的进程。
处理方法包括:
忽略。SIGKILL 和 SIGSTOP 不允许被忽略。超级用户和内核用它们强制终止进程。
捕获。signal()系统调用可用于注册信号的处理函数。
默认。通常是默认终止。
信号是异步的
信号是异步事件的典型实例。产生信号的事件对进程而言是随机出现的。
信号本质上是 int 类型数字编号
信号本质上是 int 类型的数字编号。内核对每个信号都定义了唯一的信号编号,从数字 1 开始顺序展开。每个信号都有一个宏作为信号的名字。信号宏名字与信号编号对应。
这些信号在<signum.h>头文件中定义,每个信号都是以 SIGxxx 开头。
不存在编号为 0 的信号,kill()函数 对信号编号 0 有着特殊的应用。
/* Signals. */
#define SIGHUP 1 /* Hangup (POSIX). */
#define SIGINT 2 /* Interrupt (ANSI). */
#define SIGQUIT 3 /* Quit (POSIX). */
#define SIGILL 4 /* Illegal instruction (ANSI). */
#define SIGTRAP 5 /* Trace trap (POSIX). */
#define SIGABRT 6 /* Abort (ANSI). */
#define SIGIOT 6 /* IOT trap (4.2 BSD). */
#define SIGBUS 7 /* BUS error (4.2 BSD). */
#define SIGFPE 8 /* Floating-point exception (ANSI). */
#define SIGKILL 9 /* Kill, unblockable (POSIX). */
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
#define SIGSEGV 11 /* Segmentation violation (ANSI). */
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
#define SIGPIPE 13 /* Broken pipe (POSIX). */
#define SIGALRM 14 /* Alarm clock (POSIX). */
#define SIGTERM 15 /* Termination (ANSI). */
#define SIGSTKFLT 16 /* Stack fault. */
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). */
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
#define SIGTTIN 21 /* Background read from tty (POSIX). */
#define SIGTTOU 22 /* Background write to tty (POSIX). */
#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
#define SIGPOLL SIGIO /* Pollable event occurred (System V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
#define SIGPWR 30 /* Power failure restart (System V). */
#define SIGSYS 31 /* Bad system call. */
#define SIGUNUSED 31
8.2 信号的分类
信号从可靠性分类: 可靠信号、不可靠信号。
信号从实时性分类:实时信号、非实时信号。
在 linux下使用 kill -l 可查看到所有信号。
编号1~31对应不可靠信号,34~64对应可靠信号。可以看出可靠信号使用 SIGRTMIN+N 或 SIGRTMAX-N 的方式命名。
UNIX 系统只定义了 31 种信号,而 Linux 3.x 支持 64 种信号,编号 1-64(SIGRTMIN=34, SIGRTMAX=64)。后 32 个信号表示可靠信号
8.2.1 可靠信号与不可靠信号
可靠信号支持排队,不会丢失,有信号发送函数 sigqueue()及 信号绑定函数 sigaction()。
8.2.2 实时信号与非实时信号
非实时信号都不支持排队,都是不可靠信号;实时信号都支持排队,都是可靠信号。
非实时信号(不可靠信号)也被称为标准信号。
8.3 常见信号与默认行为
标准信号(不可靠信号、非实时信号)的编号为 1~31。
SIGINT
CTRL + C,内核发送 SIGINT 信号给前台进程组中的每个进程。终止进程的运行。
SIGQUIT
CTRL + \,内核发送 SIGQUIT 信号给前台进程组中的每一 个进程。终止进程运行并生成可用于调试的核心转储文件。
SIGKILL
强制杀死进程。
ps 可以找到进程的pid号。
8.4 进程对信号的处理
忽略、捕获、默认。
系统调用 signal()和 sigaction()用于设置信号的处理方式。
signal 用于绑定信号和信号处理函数。
#include <signal.h>/* 定义sig_t 为 void* 函数指针,且有参数 */
typedef void (*sig_t)(int);/* 绑定信号和信号处理函数 */
sig_t signal(int signum, //指定要设置的信号 sig_t handler);//信号处理函数的指针/
int sigaction(int signum, //信号const struct sigaction *act, //不为NULL,表示要为信号设置新的处理方式。NULL代表无需改变当前处理方式。struct sigaction *oldact); //用来获取信号旧的处理方式。可为NULL
struct sigaction { void (*sa_handler)(int); //信号处理函数void (*sa_sigaction)(int, siginfo_t *, void *); //替代信号处理函数。与信号处理函数互斥,只能设置一个。sigset_t sa_mask;//信号掩码。被加入信号掩码的信号不能打断当前信号的执行。int sa_flags; //标志。控制信号的处理过程。void (*sa_restorer)(void);//弃用
};
两种不同状态下信号的处理方式
程序启动:
如果程序刚启动还没走到signal绑定信号和处理函数的地步,来了信号统一都是走默认操作。
程序创建:
当一个进程调用fork()创建子进程时,子进程会继承父进程的信号处理方式。
8.5 向进程发送信号
8.5.1 kill()
系统调用 kill() 可将信号发送给指定的进程或进程组中的每个进程。
#include <sys/types.h>
#include <signal.h>/*将信号发送给指定的进程*/
int kill(pid_t pid, //进程pid号int sig); //要发送的信号编号。0代表不发信号,但执行错误检查。可用于判断Pid是否存在。/*
pid 为正,则发送到 pid 指定的进程。
pid 等于 0,则发送到当前进程的进程组中的每个进程。
pid 等于-1,则发送到当前进程有权发送信号的每个进程,进程 1(init)除外。
pid 小于-1,则发送到 ID 为-pid 的进程组中的每个进程。
*/
8.5.2 raise()
库函数 raise() 用于向自身发送信号。
#include <signal.h>/*向进程自身发送信号*/
int raise(int sig);
8.6 alarm()和 pause()函数
系统调用 alarm()和 pause()。
alarm 用于设置定时器(闹钟),定时时间到了内核会向进程发送SIGALRM信号。
pause 用于使程序暂停,进入休眠状态。直到捕获一个信号为止。
#include <unistd.h>/*设置闹钟,定时时间到了内核向进程发送SIGALRM信号*/
unsigned int alarm(unsigned int seconds);//设置时间,秒单位,会覆盖。给0代表取消之前的设置。/* 使程序暂停,进入休眠状态,直到捕获一个信号为止 */
int pause(void);
8.7 信号集
信号集sigset_t 是能表示多个信号的数据类型。
# define _SIGSET_NWORDS (1024 / (8 * sizeof (unsigned long int)))typedef struct
{unsigned long int __val[_SIGSET_NWORDS];
} sigset_t;
信号机sigset_t 可以表示一组信号。可以将多个信号添加到该数据结构中。
8.7.1 初始化信号集
sigemptyset()和 sigfillset()用于初始化信号集。
sigemptyset 初始化信号集,使其不包含任何信号。
sigfillset 初始化信号集,使其包含所有信号。
#include <signal.h>/*初始化信号集,使其不包含任何信号*/
int sigemptyset(sigset_t *set);/*初始化信号集,使其包含所有信号*/
int sigfillset(sigset_t *set);
8.7.2 向信号集添加/删除信号
sigaddset()和 sigdelset()函数向信号集中添加或删除一个信号。
#include <signal.h>/* 向信号集添加信号 */
int sigaddset(sigset_t *set, int signum);/* 从信号集移除信号 */
int sigdelset(sigset_t *set, int signum);
8.7.3 测试信号是否在信号集中
sigismember()函数可以测试某一个信号是否在指定的信号集中。
#include <signal.h>int sigismember(const sigset_t *set, int signum);
8.8 获取信号的描述信息
8.8.1 strsignal函数
sys_siglist[ ]数组。
strsignal() 函数。
每个信号都有一串与之相对应的信号字符串描述信息,用于对该信号进行相应的描述。这些字符串位于 sys_siglist 数组中,sys_siglist 数组是一个 char *类型的数组。可使用信号宏名称(宏编号)作为下标获取 sys_siglist[ ] 中的描述信息。
strsignal()函数也可以获取信号描述信息。
#include <string.h>char *strsignal(int sig);
8.8.2 psignal函数
psignal(信号宏,添加信息)函数。向标准错误输出信息。
psignal()函数可以在标准错误(stderr)上输出信号描述信息。
#include <signal.h> /* 获取信号描述信息,并向标准错误输出 */
void psignal(int sig, //信号const char *s);//额外信息
8.9 信号掩码(阻塞信号传递)
内核为每一个进程维护了一个信号掩码。信号掩码其实就是一个信号集。
当进程接收到一个属于信号掩码中定义的信号时,内核会将其阻塞,不传递给进程处理,直到该信号从信号掩码中移除,该信号才会被传递给进程从而得到处理。
向进程的信号掩码添加信号的方式:
1、调用 signal()或 sigaction()函数为某一个信号设置处理方式时,进程会自动将该信号添加 到信号掩码中,保证一个信号在处理时,如果再次发生,将会被阻塞。
2、sigaction()函数设置信号处理方式时,可以额外指定一组信号添加到掩码,当调用信号处理函数时将该组信号自动添加到信号掩码中,当信号处理函数结束后再从信号掩码中移除。
3、sigprocmask()系统调用,随时可以显式地向信号掩码中添加/移除信号。
#include <signal.h>/**/
int sigprocmask(int how, //指定调用函数时的行为const sigset_t *set, //信号集sigset_t *oldset); //旧信号集。NULL代表进程信号集sigset_t/*
SIG_BLOCK:将参数 set 信号集所有信号添加到进程的信号掩码
SIG_UNBLOCK:将参数 set 信号集所有信号从进程信号掩码移除
SIG_SETMASK:参数set掩码替换进程set掩码。
*/
8.10 阻塞等待信号 sigsuspend()
更改进程的信号掩码可以阻塞所选择的信号,或解除对它们的阻塞。这种技术可以保护不希望由信号中断的关键代码段。
sigsuspend() 将设置信号掩码、pause()挂起进程、被唤醒后恢复信号掩码装成原子操作。
#include <signal.h>/* 用参数mask指向的信号集替换进程的掩码,然后pause进程,直到来信号被唤醒 */
int sigsuspend(const sigset_t *mask);
sigsuspend()相当于原子操作执行
sigprocmask(SIG_SETMASK, &mask, &old_mask);
pause();
sigprocmask(SIG_SETMASK, &old_mask, NULL);
8.11 实时信号
8.11.1 sigpending()函数
如果进程当前正在执行信号处理函数,新来的信号是进程信号掩码中的成员,那么内核会将其阻塞,将该信号添加到进程的等待信号集,
可以使用 sigpending()函数获取等待信号集中处于等待状态的信号。
#include <signal.h>/*获取线程等待信号集中的信号*/
int sigpending(sigset_t *set);
8.11.2 发送实时信号
等待信号集只是一个掩码,仅表明一个信号是否发生,而不能表示发生的次数。
换言之,如果同一个信号在阻塞状态下产生了多次,那么会将该信号记录在等待信号集中,并在之后仅当作发生了一次,这是标准信号的缺点之一。
实时信号较之于标准信号,其优势如下:
1、内核对于实时信号所采取的是队列化管理。实时信号多次发送给一个进程,会多次传递此信号。
2、发送一个实时信号时,可指定伴随数据(一整形数据或者指针值),供接收信号的进程在它的信号处理函数中获取。
3、不同实时信号按照优先级排序,使得信号传递顺序得到保障。如果多个不同的实时信号处于等待状态,将率先传递具有最小编号的信号。同优先级先来后到。
Linux 内核定义了 31 个不同的实时信号,信号编号范围为 34~64,使用 SIGRTMIN 表示编号最小的实 时信号,使用 SIGRTMAX 表示编号最大的实时信号。
应用程序当中使用实时信号,需要有以下的两点要求:
1、发送进程使用系统调用 sigqueue()向另一个进程发送实时信号以及伴随数据。
2、接收实时信号的进程要使用sigaction()为信号建立处理函数, 并加入 SA_SIGINFO 作为处理信号的标志。也就是要使用 sa_sigaction 指针指向的处理函数,才能获取信号的伴随数据。允许应用程序使用 sa_handler,但这样就不能获取到实时信号的伴随数据了。
#include <signal.h>/*进程发送实时信号*/
int sigqueue(pid_t pid,//进程pidint sig, //信号const union sigval value);//伴随数据
/*携带的伴随数据*/
typedef union sigval
{int sival_int;void *sival_ptr;
} sigval_t;
/*使用 sigaction() 绑定 信号和处理函数*/
/* 处理函数获取伴随数据 */
static void sig_handler(int sig, siginfo_t *info, void *context)
{sigval_t sig_val = info->si_value;printf("接收到实时信号: %d\n", sig);printf("伴随数据为: %d\n", sig_val.sival_int);
}
8.12 异常退出 abort()函数
exit()、_exit()或_Exit()这些函数可以终止进程,属于正常退出应用程序。
abort() 库函数用来异常退出。内核会向进程发送SIGABRT信号。
会生成核心转储文件,用于判断调用abort()时程序的状态。
核心转储文件(core dump)是当程序异常终止或崩溃时,操作系统自动生成的一个文件,它包含了程序在崩溃时的内存映像,包括堆栈、寄存器状态、堆内存、栈内存等信息。
这篇关于嵌入式Linux C应用编程指南-系统资源与信号(速记版)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!