嵌入式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_SOURCE
     char 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 申请堆内存

7.6.1 malloc、calloc、free

        在堆上分配内存:mallocfree

#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分配的内存其实是对齐的,但是对齐字节固定,而且对齐字节的边界较小。

7.6.2 malloc底层实现

        当 malloc被调用时,它会遍历空闲内存链表,查找一个足够大的空闲块来满足请求。如果找到,它会将该块分割成两部分:一部分用于满足用户请求,多余的部分保留在链表中作为新空闲块。然后,它将满足用户请求的那部分内存的地址返回给调用者。

小内存分配

        当请求的内存小于 128KB时,malloc会尝试在堆(heap)中分配内存。在这种情况下,malloc可能会使用 brk或sbrk系统调用来扩展堆的边界,或者通过维护一个空闲内存块的链表来管理内存分配。

大内存分配

        当请求的内存大小超过128KB时,malloc会倾向于使用mmap系统调用来在进程的虚拟地址空间中分配一块新的内存区域。这种方式允许分配的内存块独立于堆的当前状态,并且可以单独释放,有助于减少内存碎片。

#include <unistd.h>  

//brk() 会将堆的结束地址设置为 addr 所指向的地址,并返回 0
int brk(void *addr);
#include <sys/mman.h>

/*文件或者地址空间映射,将虚拟地址空间映射到堆上*/
void *mmap(void *addr,    //内存起始地址
           size_t length, //映射区大小。文件被映射的部分不能超过文件。
           int prot,      //映射区权限
           int flags,     //映射区操作标志.
           int fd,        //文件描述符
           off_t offset); //文件映射偏移量,通常是页大小(4KB)的整数倍

/* 解除映射 */
int munmap(void *addr, size_t length);

内存紧缩

        在某些情况下,为了回收未使用的内存并减少内存碎片,malloc实现可能会执行内存紧缩操作。就是用复制算法重新排列数据,减少内存碎片。

分配对齐内存

        标准库函数 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+NSIGRTMAX-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 发送实时信号

/*进程发送实时信号*/
int sigqueue(pid_t pid,//进程pid
                     int sig,  //信号
                     const union sigval value);//伴随数据

 sigaction() 绑定 信号和处理函数sig_Handler(int sig,

                                                                        siginfo_t *info, //获取伴随数据

                                                                         void *context)

通过 信号处理函数的 siginfo_t 参数获取 信号发送函数sigqueue 发送的伴随数据。

        等待信号集只是一个掩码,仅表明一个信号是否发生,而不能表示发生的次数

        换言之,如果同一个信号在阻塞状态下产生了多次,那么会将该信号记录在等待信号集中,并在之后仅当作发生了一次,这是标准信号的缺点之一。

实时信号较之于标准信号,其优势如下:

        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,//进程pid
             int 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)‌是当程序异常终止或崩溃时,操作系统自动生成的一个文件,它包含了程序在崩溃时的内存映像,包括堆栈、寄存器状态、堆内存、栈内存等信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大象荒野

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值