1.信号量
信号量是计算机科学中用于同步和互斥的一种抽象数据类型。在并发编程中,当多个进程或线程需要访问共享资源时,信号量用来确保资源在同一时刻只被一个进程或线程访问,从而避免竞争条件。
信号量通常具有以下特性:
-
整数值:信号量是一个非负整数,用来表示可用的资源的数量。
-
两个原子操作:
-
P操作(Proberen测试):如果信号量的值大于零,则将其减一;否则,进程或线程会被阻塞,直到信号量值变为正。
-
V操作(Verhogen增加):增加信号量的值,并唤醒等待的进程或线程。
-
根据信号量的取值,可以分为以下两种:
-
二进制信号量:其值只能是0或1,通常用于互斥。
-
计数信号量:其值可以是任何非负整数,用于表示资源的可用数量。
可用ipcs -s查看信号量:
2.信号
信号是操作系统提供的让用户(进程)给其他进程发送异步信息的一种方式,Linux定义了一系列的信号,每个信号都有一个唯一的编号和一个默认的行为。输入kill -l可以列出所有的信号,以下是一些常见的信号类型:
进程可以针对信号执行以下操作:
- 忽略:进程可以选择忽略某些信号。
- 捕获:进程可以提供一个信号处理函数(信号处理程序),当信号发生时,该函数将被调用。
- 默认行为:如果进程没有指定信号的处理方式,那么将执行信号的默认行为。
下面是简单示例代码:
cpp
#include<iostream> // 引入标准输入输出流库,用于打印信息到标准输出
#include<unistd.h> // 引入unistd.h头文件,提供对POSIX操作系统API的访问,如sleep()函数
#include<signal.h> // 引入signal.h头文件,提供信号处理的函数和宏定义
#include<sys/types.h> // 引入sys/types.h头文件,提供系统调用所需的类型定义
// 定义信号处理函数,参数signo为接收到的信号编号
void handler(int signo) {
std::cout<<"get a sig,number is:"<<signo<<std::endl; // 打印接收到的信号编号
exit(100); // 使用exit函数退出程序,返回状态码100
}
int main() {
signal(SIGINT,handler); // 将SIGINT信号(通常由Ctrl+C产生)的处理函数设置为handler
while(true) { // 创建一个无限循环
std::cout<<"I am activing...,pid:"<<getpid()<<std::endl; // 打印活动信息,包括进程ID
sleep(1); // 调用sleep函数,使程序暂停执行1秒
}
return 0; // 程序正常结束,返回0
}
使用kill函数发送信号给指定进程,返回0表示成功,-1表示失败:
cpp
#include<iostream>
#include<cerrno>
#include<cstring>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
int main(int argc, char *argv[]) {
// 检查命令行参数的数量是否为3(包括程序名称本身)
if (argc != 3) {
std::cout << "Usage:" << argv[0] << " -signumber pid" << std::endl; // 打印使用说明
return 1; // 参数数量不正确,返回错误码1
}
// 从第一个命令行参数中提取信号编号,跳过前导的'-'字符
int signumber = std::stoi(argv[1] + 1);
// 从第二个命令行参数中提取进程ID
int pid = std::stoi(argv[2]);
// 使用kill函数发送信号给指定进程,返回0表示成功,-1表示失败
int n = kill(pid, signumber);
// 检查kill函数的返回值
if (n < 0) {
// 如果返回值小于0,说明发送信号失败,打印错误信息
std::cerr << "kill error," << strerror(errno) << std::endl;
}
// 如果没有错误,程序正常结束,返回0
return 0;
}
使用raise函数发送信号给自己:
cpp
#include<iostream>
#include<cerrno>
#include<cstring>
#include<unistd.h>
#include<signal.h>
#include<sys/types.h>
using namespace std;
void handler(int signumber)
{
std::cout<<"get a sinal,number is:"<<signumber<<std::endl;
}
int main()
{
signal(2,handler);
int cnt=0;
while (true)
{
cout<<"cnt:"<<cnt++<<endl;
sleep(1);
if(cnt%5==0)
{
cout<<"send 2 to caller"<<endl;
raise(2);
}
}
}
下面是**alarm
**函数的原型和基本用法:
seconds
:指定定时器超时的时间,以秒为单位。- **返回值:**返回之前设置的定时器剩余的秒数,如果没有设置定时器,则返回0。
cpp
int g_cnt=0;
void handler(int sig)
{
cout<<"get a sig:"<<sig<<"g_cnt:"<<g_cnt<<endl;
exit(0);
}
int main()
{
signal(SIGALRM,handler);
alarm(1);
while (true)
{
g_cnt++;
}
// int cnt=0;
// while (true)
// {
// cout<<"cnt:"<<cnt++<<endl;
// }
}
异常信号:
cpp
void handler(int sig)
{
cout<<"get a sig:"<<sig<<endl;
exit(1);
}
int main()
{
signal(SIGFPE,handler);
int a=10;
a/=0;
while(true) sleep(1);
return 0;
}
1.core功能
通过ulimit -a [大小]打开Linux的core功能
在Linux系统中,**core
**功能通常指的是核心转储(core dump)功能,这是操作系统在进程崩溃或接收到特定信号时生成的一种文件,其中包含了进程内存和寄存器状态的快照。核心转储文件对于调试和故障分析非常有用,因为它可以帮助开发者或系统管理员了解导致进程崩溃的原因。
2.信号集
信号集(signal set)是Linux操作系统中用于管理信号的一种数据结构,它用于指定一组信号的集合,以便进行信号的阻塞、解除阻塞和查询操作。信号集是信号处理机制的一部分,用于处理信号的发送和接收。
信号集在内核中由**sigset_t
**类型表示,这是一个无符号整数类型的数组,其中每个元素代表一个信号编号。
** sigprocmask
**函数是 Linux 操作系统中用于信号屏蔽操作的系统调用。这个函数允许进程设置或查询当前进程的信号屏蔽字(signal mask),以决定哪些信号可以被进程接收到,哪些信号将被阻塞。
cpp
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);
-
**
how
:**指定如何操作信号屏蔽字。它可以是以下值之一:SIG_BLOCK
: 添加**set
**中指定的信号到当前进程的信号屏蔽字中。SIG_UNBLOCK
: 从当前进程的信号屏蔽字中移除**set
**中指定的信号。SIG_SETMASK
: 设置当前进程的信号屏蔽字为**set
**中指定的信号集合。
-
set
: 指向一个**sigset_t
**类型的指针,其中包含了要添加到或从信号屏蔽字中移除的信号集合。 -
oset
: 指向一个**sigset_t
** 类型的指针,如果**how
** 参数是**SIG_BLOCK
** 或**SIG_UNBLOCK
** ,则这个指针指向当前进程的信号屏蔽字,sigprocmask
函数会根据**how
** 参数的操作更新这个屏蔽字。如果**how
** 参数是**SIG_SETMASK
** ,则这个指针可以设置为**NULL
**,因为新的信号屏蔽字将直接设置到当前进程的屏蔽字中。
sigprocmask
函数返回0表示成功,返回-1表示失败,并设置**errno
**以指示错误类型。
cpp
void PrintSig(sigset_t &pending)
{
// 打印当前进程的挂起信号位图
cout << "Pending bitmap:";
// 遍历信号编号,从31(最大的信号编号)到1
for (int signo = 31; signo > 0; signo--)
{
// 使用sigismember函数检查信号是否在挂起信号集中
if (sigismember(&pending, signo))
{
// 如果信号在挂起信号集中,打印1
cout << "1";
}
else
{
// 如果信号不在挂起信号集中,打印0
cout << "0";
}
}
// 换行
cout << endl;
}
int main()
{
// 定义两个信号集变量,用于屏蔽和挂起信号
sigset_t block, oblock;
// 初始化block信号集为空集
sigemptyset(&block);
// 将信号2添加到block信号集中
sigaddset(&block, 2);
// 遍历从1到31的所有信号,并将它们添加到block信号集中
for (int signo = 1; signo <= 31; signo++)
{
sigaddset(&block, signo);
}
// 使用sigprocmask函数设置block信号集为当前进程的信号屏蔽字
int n = sigprocmask(SIG_SETMASK, &block, &oblock);
// 断言n的值为0,如果不是0,则程序会退出
assert(n == 0);
// 打印设置信号屏蔽字成功的消息
cout << "block 2 signal success" << endl;
// 创建一个无限循环,用于持续检查挂起信号
while (true)
{
// 初始化pending信号集为空集
sigset_t pending;
sigemptyset(&pending);
// 使用sigpending函数获取当前进程的挂起信号集
n = sigpending(&pending);
// 断言n的值为0,如果不是0,则程序会退出
assert(n == 0);
// 调用PrintSig函数打印挂起信号位图
PrintSig(pending);
// 暂停进程执行1秒
sleep(1);
}
}
对应于**SIGKILL
** 和**SIGSTOP
**信号,这两个信号是保留的,不能被阻塞或忽略
3.内核态和用户态
在计算机操作系统中,用户态(User Mode)和内核态(Kernel Mode)是两种不同的运行模式,它们在操作系统中扮演着不同的角色,并且具有不同的权限和功能。
用户态(User Mode)
用户态是操作系统中为普通应用程序提供的一种运行模式。在用户态中,进程只能访问自己的地址空间和有限的系统资源。用户态的主要特点是:
- 进程只能访问自己分配的内存空间,不能直接访问其他进程或内核的内存空间。
- 进程的执行受到一定的限制,不能直接执行某些操作,如访问硬件设备或修改系统配置。
- 用户态的进程通常通过系统调用来请求内核服务,如文件操作、网络通信等。
内核态(Kernel Mode)
内核态是操作系统为内核提供的一种运行模式。在内核态中,内核可以访问所有的系统资源,包括硬件设备、内存空间和文件系统。内核态的主要特点是:
- 内核可以访问所有的系统资源,包括硬件设备、内存空间和文件系统。
- 内核可以执行所有类型的操作,包括直接访问硬件设备、修改系统配置等。
- 内核态的进程通常是通过中断或系统调用从用户态切换到内核态。
信号捕捉的过程如下:
sigaction
是一个系统调用,用于设置信号的行为,包括信号处理函数、信号掩码和信号动作。这个系统调用允许用户程序对信号进行更精细的控制,包括设置信号的处理方式、屏蔽信号、设置信号动作等。
cpp
#include <signal.h>
int sigaction(int signum, const struct sigaction *restrict new_action, struct sigaction *restrict old_action);
signum
: 要设置行为的信号编号。new_action
: 指向**struct sigaction
**的指针,其中包含了新的信号行为设置。这个结构体定义了信号处理函数、信号掩码和信号动作。old_action
: 指向**struct sigaction
**的指针,用于保存旧的信号行为设置,如果需要的话。
cpp
struct sigaction {
void (*sa_handler)(int); /* 信号处理函数 */
void (*sa_sigaction)(int, siginfo_t *, void *); /* 信号处理函数,可以获取信号的附加信息 */
sigset_t sa_mask; /* 信号掩码,用于阻塞信号 */
int sa_flags; /* 信号行为的标志 */
void (*sa_restorer)(void); /* 恢复函数,通常设置为NULL */
};
使用代码如下:
cpp
void Print(sigset_t &pending)
{
// 打印当前进程的挂起信号位图
cout << "curr process pending:";
// 遍历信号编号,从31(最大的信号编号)到1
for (int sig = 31; sig >= 1; sig--)
{
// 使用sigismember函数检查信号是否在挂起信号集中
if (sigismember(&pending, sig))
{
// 如果信号在挂起信号集中,打印1
cout << "1";
}
else
{
// 如果信号不在挂起信号集中,打印0
cout << "0";
}
}
// 换行
cout << endl;
}
void handler(int signo)
{
// 信号处理函数
cout << "signal:" << signo << endl;
// 创建一个信号集,用于存储挂起的信号
sigset_t pending;
// 初始化信号集为空集
sigemptyset(&pending);
// 创建一个无限循环,不断检查是否有信号被挂起
while (true)
{
// 使用sigpending函数获取当前进程的挂起信号集
sigpending(&pending);
// 调用Print函数打印挂起信号位图
Print(pending);
// 暂停进程执行1秒
sleep(1);
}
}
int main()
{
// 定义信号处理结构体
struct sigaction act, oact;
// 设置信号处理函数为handler
act.sa_handler = handler;
// 设置信号行为的标志为0
act.sa_flags = 0;
// 初始化信号掩码为空集
sigemptyset(&act.sa_mask);
// 使用sigaction函数设置SIGINT信号的处理方式
sigaction(2, &act, &oact);
// 创建一个无限循环,不断执行
while (true)
{
// 暂停进程执行1秒
sleep(1);
}
// 程序正常结束
return 0;
}
4.volatile关键字
在C和C++编程语言中,volatile
是一个关键字,用于修饰变量或类型,以指示编译器在优化代码时不要假设该变量是常量。当一个变量被声明为**volatile
**时,编译器不会对它的读写操作进行优化,这意味着每次访问该变量时,编译器都会直接从内存中读取其值,而不是从寄存器或缓存中读取。