【Linux系统】Linux进程信号(产生,保存信号)
1. 信号快速认识
1-1 基本结论
- 如何识别信号?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。
- 信号产生之后,是知道怎么处理的,同理,如果信号没有产生,也是知道怎么处理信号的。所以,信号的处理方法,在信号产生之前,已经准备好了。
- 处理信号,立即处理吗?也可能正在做优先级更高的事情,不会立即处理。什么时候?合适的时候。
- 产生信号->识别信号->保存信号->处理信号(处理时机以及处理方式)
- 如何进行信号处理?a.默认 b.忽略 c.自定义,后续都叫做信号捕捉。
- 异步:在Linux系统中,异步是一种重要的操作系统,它允许进程在执行摸个操作时,不必等待该操作完成就可以执行其他任务,当操作完成后,系统会一某种方式通知进程。
1-2 技术应用角度的信号
1-2-1 一个样例
#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{while(true){std::cout << "I am a process, I am waiting signal!" << std::endl;sleep(1);}
}
- 用户输入命令,在Shell下启动一个前台进程。
- 用户按下Ctrl+C,这个键盘输入产生一个硬件中断,被OS获取,解释成信号,发送给目标前台进程。
- 前台进程因为收到信号,进而引起进程退出。
注意:
./test
前台进程./test &
后台进程 bash进程依旧可以进行命令行解释kill -9 PID
nohup ./test &
会在当前目录生成nohup文件,回显 [作业号] PID
1-2-2 一个系统函数
// NAME
// signal - ANSI signal handling
// SYNOPSIS
// #include <signal.h>
// typedef void (*sighandler_t)(int);
// sighandler_t signal(int signum, sighandler_t handler);
// 参数说明:
// 9 signum: 信号编号[后面解释,只需要知道是数字即可]
// 10 handler: 函数指针, 表示更改信号的处理动作,当收到对应的信号, 就回调执行handler方法
而其实,Ctrl+C的本质是向前台进程发送SIGINT即2号信号,我们证明一下,这里需要引入一个系统调用函数。
开始测试
#include <iostream>
#include <unistd.h>
#include <signal.h>void handler(int signumber)
{std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber << std::endl;
}int main()
{std::cout << "我是进程: " << getpid() << std::endl;signal(SIGINT/*2*/, handler);//仅需设置一次while(true){std::cout << "I am a process, I am waiting signal!" << std::endl;sleep(1);}
}
思考:
- 当对应的信号被触发,内核会将对应的信号编号传递自定义方法。
- 如果没有产生2号信号,则不会调用相关方法
注意
- 要注意的是,signal函数仅仅是设置了特定信号的捕捉行为处理方式,并不是直接调用处理动作。如果后续特定信号没有产生,设置的捕捉函数永远也不会被调用!!
- Ctrl+C产生的信号只能发给前台进程。一个命令后面加&可以放到后台运行,这样Shell不必等待进程结束就可以接受新的命令,启动新的进程。
- 这种控制键产生的一个前台进程和任意多个后台进程,只有前台进程才能接到像Ctrl+C 这样的信号。
- 前台进程在运行过程中用户随时可能按下Ctrl+C而产生一个信号,也就是说该进程的用户空间代码执行到任何地方都有可能收到SIGINT信号而终止,所以信号相对于进程的控制流程来说是异步(Asynchronous)的。
1-3 信号概念
信号是进程之间事件异步通知的一种方式,属于软中断。
1-3-1 查看信号
在终端执行kill -l
,可得到如下信号列表:
- 9号信号无法被捕捉。
- 发送信号的本质:就是写入信号,OS修改进程PCB中对应信号位图。
- 无论以什么样的方式发送信号,最终都是转换到OS,让OS写入信号,因为tast_struct的唯一管理者是OS 。
- 问题:OS如何知道键盘上有数据?
- 冯诺依曼体系下,输入设备对控制器触发硬件中断 (纯硬件信号电路),至此,硬件与OS可以并行了
每个信号都有一个编号和一个宏定义名称,这些宏定义可以在signal.h
中找到。
例如其中有定义:
#define SIGINT 2
/* ISO C99 signals. */
#define SIGABRT 6 /* Abnormal termination. */
#define SIGILL 4 /* Illegal instruction. */
#define SIGFPE 8 /* Erroneous arithmetic operation. */
#define SIGSEGV 11 /* Invalid access to storage. */
/* Historical signals specified by POSIX. */
#define SIGHUP 1 /* Hangup. */
#define SIGQUIT 3 /* Quit. */
#define SIGTRAP 5 /* Trace/breakpoint trap. */
#define SIGKILL 9 /* Killed. */
#define SIGSYS 12 /* Bad system call. */
#define SIGPIPE 13 /* Broken pipe. */
/* Real-time signals. */
/* Nonstandard signals. */
#define SIGURG 23 /* Urgent condition on socket. */
#define SIGCHLD 17 /* Child stopped or terminated. */
#define SIGCONT 18 /* Continue a stopped process. */
#define SIGSTOP 19 /* Stop (cannot be caught or ignored). */
#define SIGTSTP 20 /* Stop typed at terminal. */
#define SIGTTIN 21 /* Background read attempted from control terminal. */
#define SIGTTOU 22 /* Background write attempted to control terminal. */
#define SIGXCPU 24 /* CPU time limit exceeded (System V). */
#define SIGXFSZ 25 /* File size limit exceeded (System V). */
#define SIGVTALRM 26 /* Virtual timer expired. */
#define SIGPROF 27 /* Profiling timer expired. */
#define SIGWINCH 28 /* Window size change (4.3BSD, Sun). */
#define SIGIO 29 /* I/O now possible (4.2BSD). */
#define SIGPWR 30 /* Power failure restart (System V). */
#define SIGUSR1 10 /* User-defined signal 1. */
#define SIGUSR2 31 /* User-defined signal 2. */
编号34以上的是实时信号,本章只讨论编号34以下的信号,不讨论实时信号。
这些信号各自在什么条件下产生,默认的处理动作是什么,在signal(7)中都有详细说明:man 7 signal
。
Signal | Standard | Action | Comment |
---|---|---|---|
SIGABRT | P1990 | Term | Abort signal from abort(3) |
SIGBUS | P2001 | Core | Bus error (bad memory access) |
SIGCHLD | P1990 | Ign | A child stopped or terminated |
SIGCONT | - | - | Continue a stopped or paused process |
SIGFPE | P1990 | Core | Erroneous arithmetic operation |
SIGHUP | P1990 | Term | Hangup detected on controlling terminal |
SIGILL | P1990 | Core | Illegal Instruction |
SIGINFO | P1990 | Term | A synonym for SIGKILL (2BSD) |
SIGINT | P1990 | Term | Interrupt from keyboard |
SIGIO | P1990 | Core | I/O now possible; see signal(7) |
SIGIOT | P1990 | Term | IOT trap. A synonym for SIGABRT |
SIGKILL | P1990 | Term | Kill (cannot be caught or ignored) |
SIGPIPE | P1990 | Term | Broken pipe: write to pipe with no readers; see pipe(7) |
SIGPROF | P2001 | Term | Profiling timer expired |
SIGPWR | - | - | Power failure (System V) |
SIGQUIT | P1990 | Core | Quit from keyboard |
SIGSEGV | P1990 | Core | Invalid memory reference |
SIGSTOP | P1990 | Stop | Stop process (cannot be caught or ignored) |
SIGTSTP | P1990 | Stop | Stop typed at terminal |
SIGTERM | P1990 | Term | Termination signal |
SIGTRAP | P2001 | Core | Trace/breakpoint trap |
SIGTTIN | P1990 | Stop | Background read attempted from control terminal |
SIGTTOU | P1990 | Stop | Background write attempted to control terminal |
SIGURG | P1990 | Ign | Urgent condition on socket (4.2BSD) |
SIGUSR1 | P1990 | Term | User-defined signal 1 |
SIGUSR2 | P1990 | Term | User-defined signal 2 |
SIGXCPU | P2001 | Core | CPU time limit exceeded (4.2BSD) |
SIGXFSZ | P2001 | Core | File size limit exceeded (4.2BSD) |
SIGWINCH | - | Ign | Window resize signal (4.3BSD, Sun) |
1-3-2 信号处理
信号处理函数稍后详细介绍,可选的处理动作有以下三种:
- 忽略此信号
#include <iostream>
#include <unistd.h>
#include <signal.h>void handler(int signumber)
{std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber << std::endl;
}int main()
{std::cout << "我是进程: " << getpid() << std::endl;signal(SIGINT/*2*/, SIG_IGN); // 设置忽略信号的宏while(true){std::cout << "I am a process, I am waiting signal!" << std::endl;sleep(1);}
}
- 执行该信号的默认处理动作
#include <iostream>
#include <unistd.h>
#include <signal.h>void handler(int signumber)
{std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber << std::endl;
}int main()
{std::cout << "我是进程: " << getpid() << std::endl;signal(SIGINT/*2*/, SIG_DFL); // 设置默认信号的宏while(true){std::cout << "I am a process, I am waiting signal!" << std::endl;sleep(1);}
}
- 提供一个信号处理函数,要求内核在处理该信号时切换到用户态执行这个处理函数,这种方式称为自定义(就是开始的样例)
// 就是开始的样例
#include <iostream>
#include <unistd.h>
#include <signal.h>void handler(int signumber)
{std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber << std::endl;
}int main()
{std::cout << "我是进程: " << getpid() << std::endl;signal(SIGINT/*2*/, handler);while(true){std::cout << "I am a process, I am waiting signal!" << std::endl;sleep(1);}
}
注意看源码:
#define SIG_DFL ((__sighandler_t) 0) /* Default action. */
#define SIG_IGN ((__sighandler_t) 1) /* Ignore signal. */
typedef void (*__sighandler_t)(int);
// 其实SIG_DFL和SIG_IGN就是把0、1 强转为函数指针类型
为了保证条理,我们采用如下思路来进行阐述:
2.产生信号
2-1 通过终端按键产生信号
2-1-1 基本操作
Ctrl+C
(SIGINT)已经验证过,这里不再重复。Ctrl+\
(SIGQUIT)可以发送终止信号并生成core dump文件,用于事后调试)。
#include <iostream>
#include <unistd.h>
#include <signal.h>void handler(int signumber)
{std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber << std::endl;
}int main()
{std::cout << "我是进程: " << getpid() << std::endl;signal(SIGQUIT/*3*/, handler); //第13行代码while(true){std::cout << "I am a process, I am waiting signal!" << std::endl;sleep(1);}
}
编译运行,输出:
我是进程: 213056
I am a process, I am waiting signal!
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^我是: 213056, 我获得了一个信号: 3
注释掉第13行代码后:
我是进程: 213146
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^Quit
Ctrl+Z
(SIGTSTP)可以发送停止信号,将当前前台进程挂起到后台等。
#include <iostream>
#include <unistd.h>
#include <signal.h>void handler(int signumber)
{std::cout << "我是: " << getpid() << ", 我获得了一个信号: " << signumber << std::endl;
}int main()
{std::cout << "我是进程: " << getpid() << std::endl;signal(SIGTSTP/*20*/, handler);while(true){std::cout << "I am a process, I am waiting signal!" << std::endl;sleep(1);}
}
编译运行,输出:
我是进程: 213552
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^Z我是: 213552, 我获得了一个信号: 20
注释掉第13行代码后:
我是进程: 213627
I am a process, I am waiting signal!
I am a process, I am waiting signal!
I am a process, I am waiting signal!
^Z
[1]+ Stopped ./sig
whb@bite:~/code/test$ jobs
[1]+ Stopped ./sig
2-1-2 理解OS如何得知键盘有数据
键盘按下产生电信号,向CPU发送硬件中断,CPU检测到中断信号后,执行操作系统中处理键盘数据的代码,操作系统从外设读入数据到内存,等待进一步处理。
2-1-3 初步理解信号起源
注意:
- 信号其实是从纯软件角度,模拟硬件中断的行为。
- 只不过硬件中断是发给CPU,而信号是发给进程。
- 两者有相似性,但是层级不同,这点我们后面的感觉会更加明显。
2-2 调用系统命令向进程发信号
示例代码:
#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{while(true){sleep(1);}
}
操作步骤:
$ g++ sig.cc -o sig
$./sig &
(在后台执行死循环程序)$ ps ajx |head -1 && ps ajx | grep sig
(查找进程信息)
输出:
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
211805 213784 213784 211805 pts/0 213792 S 1002 0:00./sig
$ kill -SIGSEGV 213784
(给进程发送SIGSEGV信号)
多按一次回车后显示:
[1]+ Segmentation fault ./sig
说明:
-
213784是sig进程的pid。
-
之所以要再次回车才显示Segmentation fault,是因为在213784进程终止之前已经回到了Shell提示符等待用户输入下一条命令,Shell不希望Segmentation fault信息和用户的输入交错在一起,所以等用户输入命令之后才显示。
-
指定发送某种信号的kill命令可以有多种写法,上面的命令还可以写成
kill -11 213784
,11是信号SIGSEGV的编号。以往遇到的段错误都是由非法内存访问产生的,而这个程序本身没错,给它发SIGSEGV也能产生段错误。
2-3 使用函数产生信号
2-3-1 kill
kill命令是调用kill函数实现的。kill函数可以给一个指定的进程发送指定的信号。
- NAME:kill - send signal to a process
- SYNOPSIS
#include <sys/types.h>
#include <signal.h>int kill(pid_t pid, int sig);
- RETURN VALUE:On success (at least one signal was sent), zero is returned. On error, -1 is returned, and errno is set appropriately.
样例:实现自己的kill命令
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>int main(int argc, char *argv[])
{if(argc != 3){std::cerr << "Usage: " << argv[0] << " -signumber pid" << std::endl;return 1;}int number = std::stoi(argv[1]+1); // 去掉-pid_t pid = std::stoi(argv[2]);int n = kill(pid, number);return n;
}
2-3-2 raise
raise函数可以给当前进程发送指定的信号(自己给自己发信号)。
- NAME:raise - send a signal to the caller
- SYNOPSIS
#include <signal.h>int raise(int sig);
- RETURN VALUE:raise() returns 0 on success, and nonzero for failure.
样例
#include <iostream>
#include <unistd.h>
#include <signal.h>void handler(int signumber)
{// 整个代码就只有这一处打印std::cout << "获取了一个信号: " << signumber << std::endl;
}int main()
{signal(2, handler); // 先对2号信号进行捕捉// 每隔1s,自己给自己发送2号信号while(true){sleep(1);raise(2);}
}
编译运行,输出:
获取了一个信号: 2
获取了一个信号: 2
获取了一个信号: 2
2-3-3 abort
abort函数使当前进程接收到信号而异常终止。
- NAME:abort - cause abnormal process termination
- SYNOPSIS
#include <stdlib.h>void abort(void);
- RETURN VALUE:The abort() function never returns. // 就像exit函数一样,abort函数总是会成功的,所以没有返回值。
样例
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>void handler(int signumber)
{// 整个代码就只有这一处打印std::cout << "获取了一个信号: " << signumber << std::endl;
}int main()
{signal(SIGABRT, handler);while(true){sleep(1);abort();}
}
编译运行,输出:
获取了一个信号: 6 // 实验可以得知,abort给自己发送的是固定6号信号,虽然捕捉了,但是还是要退出
Aborted
注释掉第15行代码后:
Aborted
2-4 由软件条件产生信号
SIGPIPE是一种由软件条件产生的信号,在“管道”中已经介绍过了。本节主要介绍alarm函数和SIGALRM信号。
- NAME:alarm - 设置用于传递信号的闹钟
- SYNOPSIS
#include <unistd.h>unsigned int alarm(unsigned int seconds);
- RETURN VALUE:alarm() 返回距离发送任何先前计划的警报剩余的秒数,如果之前没有预先计划的警报,则返回 0。
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM
信号,该信号的默认处理动作是终止当前进程。
这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。打个比方,某人要小睡一觉,设定闹钟为30分钟之后响,20分钟后被人吵醒了,还想多睡一会儿,于是重新设定闹钟为15分钟之后响,“以前设定的闹钟时间还余下的时间”就是10分钟。
如果seconds值为0,表示取消以前设定的闹钟,函数的返回值仍然是以前设定的闹钟时间还余下的秒数。
2-4-1 基本alarm验证 - 体会IO效率问题
IO多的代码:
// IO多
#include <iostream>
#include <unistd.h>
#include <signal.h>int main()
{int count = 0;alarm(1);while(true){std::cout << "count : " << count << std::endl;count++;}return 0;
}
IO少的代码:
// IO少
#include <iostream>
#include <unistd.h>
#include <signal.h>int count = 0;
void handler(int signumber)
{std::cout << "count : " << count << std::endl;exit(0);
}int main()
{signal(SIGALRM, handler);alarm(1);while(true){count++;}return 0;
}
运行结果:
count : 107148
count : 107149
Alarm clock
$ g++ alarm.cc -o alarm
whb@bite:~/code/test$ ./alarm
count : 492333713
结论:
- 闹钟会响一次,默认终止进程。
- 有IO时效率低。
2-4-2 设置重复闹钟
代码样例:
#include <iostream>
#include <unistd.h>
#include <signal.h>
#include <vector>
#include <functional>using func_t = std::function<void()>;int gcount = 0;
std::vector<func_t> gfuncs;void handler(int signo)
{for(auto &f : gfuncs){f();}std::cout << "gcount : " << gcount << std::endl;int n = alarm(1); // 重设闹钟,会返回上一次闹钟的剩余时间std::cout << "剩余时间 : " << n << std::endl;
}int main()
{gfuncs.push_back([](){ std::cout << "我是一个内核刷新操作" << std::endl; });gfuncs.push_back([](){ std::cout << "我是一个检测进程时间片的操作,如果时间片到了,我会切换进程" << std::endl; });gfuncs.push_back([](){ std::cout << "我是一个内存管理操作,定期清理操作系统内部的内存碎片" << std::endl; });alarm(1); // 一次性的闹钟,超时alarm会自动被取消signal(SIGALRM, handler);while(true){pause();std::cout << "我醒来了..." << std::endl;gcount++;}
}
// NAME
pause - wait for signal
// SYNOPSIS
#include <unistd.h>int pause(void);
// DESCRIPTION
pause() 使调用进程(或线程)休眠,直到传递终止进程或导致调用信号捕获函数的信号。RETURN VALUE
pause()仅在捕获到信号并返回信号捕获函数时返回。在这种情况下,pause() 返回 -1,并且 errno 设置为 EINTR。
运行结果(窗口1):
我的进程pid是: 216982
剩余时间 : 13 // 提前唤醒它,剩余
剩余时间 : 0
剩余时间 : 0
剩余时间 : 0
剩余时间 : 0
(窗口2):
$ kill -14 216982
结论:
- 闹钟设置一次,起效一次。
- 重复设置的方法。
- 如果时间允许,可以测试一下alarm(0) 。
2-4-3 如何理解软件条件
在操作系统中,信号的软件条件指的是由软件内部状态或特定软件操作触发的信号产生机制。这些条件包括但不限于定时器超时(如alarm函数设定的时间到达)、软件异常(如向已关闭的管道写数据产生的SIGPIPE信号)等。当这些软件条件满足时,操作系统会向相关进程发送相应的信号,以通知进程进行相应的处理。简而言之,软件条件是因操作系统内部或外部软件操作而触发的信号产生。
2-4-4 如何简单快速理解系统闹钟
系统闹钟,其实本质是OS必须自身具有定时功能,并能让用户设置这种定时功能,才可能实现闹钟这样的机制。现代Linux提供了定时功能,定时器也要被管理:先描述,再组织。内核中的定时器数据结构是:
struct timer_list {struct timer_list_entry entry;unsigned long expires;void (*function)(unsigned long);unsigned long data;struct vec_base *base;
};
我们在这部分进行深究,为了理解它,我们可以看到:定时器超时时间expires和处理方法function。操作系统管理定时器,采用的是时间轮的做法,但是我们为了简单理解,可以把它在组织成为“堆结构”。
2-5 硬件异常产生信号
硬件异常被硬件以某种方式检测到并通知内核,然后内核向当前进程发送适当的信号。
例如当前进程执行了除以0的指令,CPU的运算单元会产生异常,内核将这个异常解释为SIGFPE信号发送给进程。
再比如当前进程访问了非法内存地址,MMU会产生异常,内核将这个异常解释为SIGSEGV信号发送给进程。
2-5-1 模拟除0
#include <stdio.h>
#include <signal.h>void handler(int sig)
{printf("catch a sig : %d\n", sig);
}int main()
{//signal(SIGFPE, handler); // 8) SIGFPEsleep(1);int a = 10;a/=0;a/=0;while(1);return 0;
}
2-5-2 模拟野指针
默认行为
#include <stdio.h>
#include <signal.h>void handler(int sig)
{printf("catch a sig : %d\n", sig);
}int main()
{//signal(SIGSEGV, handler);sleep(1);int *p = NULL;*p = 100;while(1);return 0;
}
运行结果:
$./sig
Segmentation fault (core dumped)
捕捉行为
#include <stdio.h>
#include <signal.h>void handler(int sig)
{printf("catch a sig : %d\n", sig);
}int main()
{signal(SIGSEGV, handler);sleep(1);int *p = NULL;*p = 100;while(1);return 0;
}
运行结果:
$./sig
catch a sig : 11
catch a sig : 11
catch a sig : 11
由此可以确认,在C/C++中当除零、内存越界等异常,在系统层面上,是被当成信号处理的。
注意:
通过上面的实验,我们可能发现:一直有信号产生被捕获。
- 实际上OS会检查应用程序的异常情况,CPU中有控制和状态寄存器,用于控制处理器操作,状态寄存器有一些标志位,OS会检测是否存在异常状态,有异常存在就会调用对应的异常处理方法。
- 除零异常后,未清理内存等,CPU中保留上下文数据及寄存器内容,所以异常会一直存在,持续发出异常信号。访问非法内存也是如此。
- OS系统要不要知道CPU内部出错了?是要知道的。所以必须知道是哪一个进程导致的,并通过信号杀掉进程
2-5-3 子进程退出core dump
在Linux系统中,进程异常终止时可能会进行Core Dump(核心转储),即把进程当时的内存状态记录下来,保存在一个文件中,通常命名为 core.PID
(PID为进程号 ) 。而core dump标志位是与进程退出相关的一个标识,具体如下:
- 位置与含义:在存储进程退出码的变量内部结构中,次第8位表示退出码,最低7位表示终止信号,终止信号的前一位就是core dump标志位 。该标志位用于表示进程收到信号后的动作是Core(核心转储)还是Term(终止) ,一般0表示进程是Core退出(进行核心转储 ),1表示Term退出(仅终止进程 )。
- 与信号及Core Dump的关联:并非所有信号引起的进程退出都会产生Core Dump文件,只有带有
core
标志的信号(如SIGSEGV
段错误信号 )引起的退出,在core dump功能开启时才会产生。当core dump开启,遇到有core
标志的信号,进程会进行核心转储,且退出码里的core dump标志位会被置为1 ;若core dump关闭,即便遇到有core
标志的信号,也不会进行核心转储,标志位始终为0 。 - 查看与设置:可通过
ulimit -a
查看当前系统资源限制,其中core file size
表示core dump功能相关文件大小限制,若其大小为0,说明系统默认关闭core dump功能 。可使用ulimit -c
命令设置生成的core文件大小的最大限制来开启该功能,如ulimit -c 10240
可将core文件大小限制设为10240 blocks 。
core dump标志位是判断进程异常退出时是否进行核心转储的重要标识,对程序调试定位问题根源有重要意义。
#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>int main()
{if (fork() == 0){sleep(1);int a = 10;a /= 0;exit(0);}int status = 0;waitpid(-1, &status, 0);printf("exit signal: %d, core dump: %d\n", status&0x7F, (status>>7)&1);return 0;
}
列出部分信号及其属性:
信号 | 进程 | 标志 | 描述 |
---|---|---|---|
SIGABRT | P1990 | Core | Abort signal from abort(3) |
SIGALRM | P1990 | Term | Timer signal from alarm(2) |
SIGBUS | P2001 | Core | Bus error (bad memory access) |
SIGCHLD | P1990 | Ign | Child stopped or terminated |
SIGCONT | P1990 | Cont | A synonym for SIGCHLD |
SIGFPE | P1990 | Core | Floating - point exception |
SIGGPE | - | Term | Emulator trap |
SIGHPF | P1990 | Core | Hangup detected on controlling terminal |
SIGILL | P1990 | Core | Illegal Instruction |
SIGINFO | - | - | A synonym for SIGPWR |
SIGINT | P1990 | Term | Interrupt from keyboard |
SIGIO | - | Term | I/O now possible (4.2BSD) |
SIGKILL | P1990 | Core | Kill signal. A synonym for SIGABRT |
SIGLOST | - | Term | File lock lost (unused) |
SIGPIPE | P1990 | Term | Broken pipe: write to pipe with no reader |
SIGPOLL | P2001 | Term | Pollable event (Sys V) |
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 65536
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real - time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7643
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
2-5-4 Core Dump
- SIGINT的默认处理动作是终止进程,SIGQUIT的默认处理动作是终止进程并且Core Dump
- 首先解释什么是Core Dump。当一个进程要异常终止时,可以选择把进程的用户空间内存数据全部保存到磁盘上,文件名通常是core,这叫做Core Dump。
- 进程异常终止通常是因为有Bug,比如非法内存访问导致段错误,事后可以用调试器检查core文件以查清错误原因,这叫做Post-mortem Debug(事后调试)。
- 一个进程允许产生多大的core文件取决于进程的Resource Limit(这个信息保存在PCB中)。默认是不允许产生core文件的,因为core文件中可能包含用户密码等敏感信息,不安全。
- 在开发调试阶段可以用ulimit命令改变这个限制,允许产生core文件。首先用ulimit命令改变Shell进程的Resource Limit,如允许core文件最大为1024K:$ ulimit -c 1024
$ ulimit -c 1024
$ ulimit -a
core file size (blocks, -c) 1024
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 7643
max locked memory (kbytes, -l) 65536
max memory size (kbytes, -m) unlimited
open files (-n) 65535
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real - time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 7643
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited
然后写一个死循环程序:
#include <stdio.h>int main()
{printf("pid is : %d\n", getpid());while(1);return 0;
}
前台运行这个程序,然后在终端键入Ctrl-C(貌似不行)或Ctrl-\(这个可以):
#./test
pid is : 4586
^\Quit (core dumped)
# ll
total 92
-rw-------. 1 root root 159744 Apr 21 18:04 core.4586
-rwxr-xr-x. 1 root root 61 Apr 21 18:00 Makefile
-rw-r--r--. 1 root root 4766 Apr 21 18:03 test.c
-rw-r--r--. 1 root root 92 Apr 21 18:03 test.c
-rw-r--r--. 1 root root 627 Apr 21 17:36 test.cpp
[root@localhost test11]#
ulimit命令改变了Shell进程的Resource Limit,test进程的PCB由Shell进程复制而来,所以也具有和Shell进程相同的Resource Limit值,这样就可以产生Core Dump了。使用core文件:
#./test
Segmentation fault (core dumped)
# ls
core.2884 Makefile test test.c
# gdb test core.2884
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-75.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /BIT/project/test78/test...done.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /BIT/project/test78/test...done.
(gdb) core-file core.2884
[New Thread 2884]
Missing separate debuginfo for
Try: yum --enablerepo='*debug*' install /usr/lib/debug/build-id/d5/942f21cf3002919e5518a033793d3c25a660f3.debug
Reading symbols from /lib/libc-2.12.so...Reading symbols from /usr/lib/debug/lib/libc-2.12.so.debug...done.
done.
Reading symbols from /lib/ld-2.12.so...Reading symbols from /usr/lib/debug/lib/ld-2.12.so.debug...done.
done.
Loaded symbols for /lib/ld-2.12.so
Core was generated by `./test'.
Program terminated with signal 11, Segmentation fault.
#0 0x080483ad in fun () at test.c:8
8 *p = 100;
(gdb)
3. 保存信号
3-1 常见概念
- 实际执行信号的处理动作称为信号送达(Delivery)。
- 信号从产生到送达之间的状态,称为信号未决(Pending)。
- 进程可以选择阻塞(Block)某个信号。
- 被阻塞的信号产生时将保持在未决状态,直到进程解除对此信号的阻塞,才执行送达的动作。
- 注意,阻塞和忽略是不同的,只要信号被阻塞就不会送达,而忽略是在送达之后可选的一种处理动作。
3-2 在内核中的表示
- 每个信号都有两个标志位分别表示阻塞(block)和未决(pending),还有一个函数指针表示处理动作。
- 信号产生时,内核在进程控制块中设置该信号的未决标志,直到信号送达才清除该标志。 在图中例子,SIGHUP信号未阻塞也未产生,当它送达时执行默认处理动作。
- SIGINT信号产生过,但正在被阻塞,所以暂时不能送达。虽然它的处理动作是忽略,但在没有解除阻塞之前不能忽略这个信号,因为进程仍有机会改变处理动作之后再解除阻塞。
- SIGQUIT信号未产生,一旦产生SIGQUIT信号将被阻塞,它的处理动作是用户自定义函数sighandler。
如果在进程解除对某信号的阻塞之前该信号产生过多次,POSIX.1允许系统递送该信号一次或多次。Linux是这样实现的:常规信号在送达之前产生多次只计一次,而实时信号在送达之前产生多次可以依次放在一个队列里。本章不讨论实时信号。
// 内核结构 2.6.18
struct task_struct {.../* signal handlers */struct sighand_struct *sighand;sigset_t blocked;struct sigpending pending;...
};struct sighand_struct {atomic_t count;struct k_sigaction action[_NSIG]; // #define _NSIG 64spinlock_t siglock;
};struct __new_sigaction {__sighandler_t sa_handler;unsigned long sa_flags;void (*sa_restorer)(void); /* Not used by Linux/SPARC */__new_sigset_t sa_mask;
};struct k_sigaction {struct __new_sigaction sa;void __user *ka_restorer;
};/* Type of a signal handler. */
typedef void (*__sighandler_t)(int);struct sigpending {struct list_head list;sigset_t signal;
};
3-3 sigset_t
从图来看,每个信号只有一个bit的未决标志,非0即1,不记录该信号产生了多少次,阻塞标志也是这样表示的。
因此,未决和阻塞标志可以用相同的数据类型sigset_t
来存储,sigset_t
称为信号集,这个类型可以表示每个信号的“有效”或“无效”状态,在阻塞信号集中“有效”和“无效”的含义是该信号是否被阻塞,而在未决信号集中“有效”和“无效”的含义是该信号是否处于未决状态。
下一节将详细介绍信号集的各种操作。阻塞信号集也叫做当前进程的信号屏蔽字(Signal Mask),这里的“屏蔽”应该理解为阻塞而不是忽略。
3-4 信号集操作函数
sigset_t类型对于每种信号用一个bit表示“有效”或“无效”状态,至于这个类型内部如何存储这些bit则依赖于系统实现,从使用者的角度是不必关心的,使用者只能调用以下函数来操作sigset_t变量,而不应该对它的内部数据做任何解释,比如用printf直接打印sigset_t变量是没有意义的。
#include <signal.h>
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);
- 函数sigemptyset初始化set所指向的信号集,使其中所有信号的对应bit清零,表示该信号集不包含任何有效信号。
- 函数sigfillset初始化set所指向的信号集,使其中所有信号的对应bit置位,表示该信号集的有效信号包括系统支持的所有信号。
- 注意,在使用sigset_t类型的变量之前,一定要调用
sigemptyset
或sigfillset
做初始化,使信号集处于确定的状态。初始化sigset_t变量之后就可以在调用sigaddset和sigdelset在该信号集中添加或删除某种有效信号。
这四个函数都是成功返回0,出错返回-1。sigismember是一个布尔函数,用于判断一个信号集的有效信号中是否包含某种信号,若包含则返回1,不包含则返回0,出错返回-1。
3-4-1 sigprocmask
调用函数sigprocmask可以读取或更改进程的信号屏蔽字(阻塞信号集)。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
// 返回值:若成功则为0,若出错则为-1
-
如果oset是非空指针,则读取进程的当前信号屏蔽字通过oset参数传出。
-
如果set是非空指针,则更改进程的信号屏蔽字,参数how指示如何更改。
-
如果oset和set都是非空指针,则先将原来的信号屏蔽字备份到oset里,然后根据set和how参数更改信号屏蔽字。
假设当前的信号屏蔽字为mask,下表说明了how参数的可选值。
SIG_BLOCK | set包含了我们希望添加到当前信号屏蔽字的信号,相当于mask=mask|set |
SIG_UNBLOCK | set包含了我们希望从当前信号屏蔽字中解除阻塞的信号,相当于mask=mask&~set |
SIG_SETMASK | 设置当前信号屏蔽字为set所指向的值,相当于mask=set |
如果调用sigprocmask解除了对当前若干个未决信号的阻塞,则在sigprocmask返回前,至少将其中一个信号送达。
#include <signal.h>
int sigpending(sigset_t *set);
读取当前进程的未决信号集,通过set参数传出。调用成功则返回0,出错则返回-1。
下面用刚学的几个函数做个实验。程序如下:
#include <iostream>
#include <unistd.h>
#include <cstdio>
#include <sys/types.h>
#include <sys/wait.h>void PrintPending(sigset_t &pending)
{std::cout << "curr process[" << getpid() << "]pending: ";for (int signo = 31; signo >= 1; signo--){if (sigismember(&pending, signo)){std::cout << 1;}else{std::cout << 0;}}std::cout << "\n";
}void handler(int signo)
{std::cout << signo << " 信号被送达!!!" << std::endl;std::cout << "-------------------------" << std::endl;sigset_t pending;sigpending(&pending);PrintPending(pending);std::cout << "-------------------------" << std::endl;
}int main()
{// 0. 捕捉2号信号signal(2, handler); // 自定义捕捉//signal(2, SIG_IGN); // 忽略一个信号//signal(2, SIG_DFL); // 信号的默认处理动作// 1. 屏蔽2号信号sigset_t block_set, old_set;sigemptyset(&block_set);sigemptyset(&old_set);sigaddset(&block_set, SIGINT); // 我们有没有修改当前进行的内核block表呢??1 0// 1.1 设置进入进程的Block表中sigprocmask(SIG_BLOCK, &block_set, &old_set); // 真正的修改当前进行的内核block表,完成了对2号信号的屏蔽!int cnt = 15;while (true){// 2. 获取当前进程的pending信号集sigset_t pending;sigpending(&pending);// 3. 打印pending信号集PrintPending(pending);cnt--;// 4. 解除对2号信号的屏蔽if (cnt == 0){std::cout << "解除对2号信号的屏蔽!!!" << std::endl;sigprocmask(SIG_SETMASK, &old_set, &block_set);}sleep(1);}
}
运行结果:
$./run
curr process[448336]pending: 00000000000000000000000000000000
curr process[448336]pending: 00000000000000000000000000000000
curr process[448336]pending: 00000000000000000000000000000000
^Ccurr process[448336]pending: 00000000000000000000000000000010
curr process[448336]pending: 00000000000000000000000000000010
curr process[448336]pending: 00000000000000000000000000000010
curr process[448336]pending: 00000000000000000000000000000010
curr process[448336]pending: 00000000000000000000000000000010
curr process[448336]pending: 00000000000000000000000000000010
curr process[448336]pending: 00000000000000000000000000000010
程序运行时,每秒钟把各信号的未决状态打印一遍,由于我们阻塞了SIGINT信号,按Ctrl-C将会使SIGINT信号处于未决状态,按Ctrl-\仍然可以终止程序,因为SIGQUIT信号没有阻塞。
相关文章:
【Linux系统】Linux进程信号(产生,保存信号)
1. 信号快速认识 1-1 基本结论 如何识别信号?识别信号是内置的,进程识别信号,是内核程序员写的内置特性。信号产生之后,是知道怎么处理的,同理,如果信号没有产生,也是知道怎么处理信号的。所以…...
llamafactory-cli webui启动报错TypeError: argument of type ‘bool‘ is not iterable
一、问题 在阿里云NoteBook上启动llamafactory-cli webui报错TypeError: argument of type ‘bool’ is not iterable This share link expires in 72 hours. For free permanent hosting and GPU upgrades, run gradio deploy from the terminal in the working directory t…...
工 厂 模 式
冷知识,当我们需要使用平底锅时,我们并不需要知道平底锅是怎么造的,坏了只需要再买就好了。至于造平底锅,全部交给我们的生产工厂就好。 蕴含这种创建对象思路的设计方法,我们称为“工厂模式”。 核心思想 工厂模式&…...
synchronized与Lock深度对比
Java并发编程:synchronized与Lock深度对比 基本概念 1.1 synchronized synchronized是Java内置的关键字,属于JVM层面的锁机制。它通过对象监视器(Monitor)实现同步,具有自动获取和释放锁的特性。 // 同步方法 public synchronized void sy…...
LeetCode —— 94. 二叉树的中序遍历
94. 二叉树的中序遍历 题目:94. 二叉树的中序遍历 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) :…...
【无标题】四色拓扑收缩模型中环形套嵌结构的颜色保真确定方法
#### **1. 环形嵌套结构的局部保真机制** - **零点虚边与环形嵌套**:在顶点 \( v \) 处引入环形嵌套结构(如环面 \( T^2 \)),通过虚边连接形成闭合路径。该结构作为“颜色记忆单元”,存储相邻区域的色彩信息࿰…...
Curl 全面使用指南
Curl(Client URL)是一个跨平台命令行工具,支持多种协议(HTTP/HTTPS/FTP/SFTP等),用于数据传输、API调试、文件上传/下载等场景。以下从 核心功能、用户疑问解答、高级技巧 三方面系统总结,并整合…...
vscode 的空格和 tab 设置 与 Rime 自建词库
自动保存(多用于失去焦点时保存) Files: Auto Save 推荐不勾 保存时格式化(Pritter 插件的功能,自动使用 Pritter 的格式) Editor: Format On Save 推荐不勾 tab 的空格数量,2 或 4 Editor: Tab Size 推荐…...
Spark-小练试刀
任务1:HDFS上有三份文件,分别为student.txt(学生信息表)result_bigdata.txt(大数据基础成绩表), result_math.txt(数学成绩表)。 加载student.txt为名称为student的RDD…...
Python爬虫实战:获取jd商城最新5060ti 16g显卡销量排行榜商品数据并做分析,为显卡选购做参考
一、引言 1.1 研究目的 本研究旨在利用 Python 爬虫技术,从京东商城获取 “5060ti 16g” 型号显卡的商品数据,并对这些数据进行深入分析。具体目标包括: 实现京东商城的模拟登录,突破登录验证机制,获取登录后的访问权限。高效稳定地爬取按销量排名前 20 的 “5060ti 16g…...
【Vue bug】:deep()失效
vue 组件中使用了 element-plus 组件 <template><el-dialog:model-value"visible":title"title":width"width px":before-close"onClose"><div class"container" :style"{height:height px}"&g…...
基于数字图像处理的裂缝检测与识别系统(Matlab)
【优化】Matlab裂缝检测与识别系统 基于数字图像处理的裂缝检测与识别系统(Matlab) (基本常在线秒回,有兴趣可以随时联系博主) 系统主要的内容包括: 1.图像加载与初始化 选择图像文件并加载:…...
day12:遗传算法及常见优化算法分享
遗传算法这些常见优化算法简直是 “宝藏素材”!用好了,轻轻松松就能填满论文一整节内容;要是研究透彻,甚至能独立撑起一整个章节。今天不打算深入展开,有个基础认知就行。等之后写论文真要用到这些算法了,咱…...
【计算机视觉】语义分割:MMSegmentation:OpenMMLab开源语义分割框架实战指南
深度解析MMSegmentation:OpenMMLab开源语义分割框架实战指南 技术架构与设计哲学系统架构概览核心技术特性 环境配置与安装指南硬件配置建议详细安装步骤环境验证 实战全流程解析1. 数据集准备2. 配置文件定制3. 模型训练与优化4. 模型评估与推理 核心功能扩展1. 自…...
25_04_30Linux架构篇、第1章_02源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62
Linux_基础篇 欢迎来到Linux的世界,看笔记好好学多敲多打,每个人都是大神! 题目:源码编译安装Apache HTTP Server 最新稳定版本是 2.4.62 版本号: 1.0,0 作者: 老王要学习 日期: 2025.05.01 适用环境: Centos7 文档说明 本文…...
【重走C++学习之路】25、特殊类设计
目录 一、不能被拷贝的类 二、堆上创建对象的类 三、栈上创建对象的类 四、不能被继承的类 五、单例模式 结语 一、不能被拷贝的类 如何实现一个不能被拷贝的类?在看到这个要求的第一反应就是禁掉类的拷贝构造函数和赋值运算符重载函数,再往深了探…...
基于Redis实现-用户签到
基于Redis实现-用户签到 这个功能将使用到Redis中的BitMap来实现。 我们按照月来统计用户签到信息,签到记录为1,未签到则记录为0 把每一个bit位对应当月的每一天,形成了映射关系。用0和1标示业务状态,这种思路称为位图(BitMap)。…...
利用Redisson分布式锁解决多服务器数据刷新问题
利用Redisson分布式锁解决多服务器数据刷新问题 一、业务背景二、代码实现1、引入Redisson依赖2、配置Redisson,实际项目中Redis为集群配置3、自定义拒绝策略4、异步刷新网元服务 三、项目结构及源码 一、业务背景 最近有个需求需要自动刷新网元服务,由…...
25.4.30数据结构|并查集 路径压缩
前言 在QuickUnion快速合并的过程中,每次都要找根ID,而路径压缩让找根ID变得更加迅速直接。 路径压缩 针对的是findRootIndex()【查找根ID】进行的压缩。 需要实现的是: 在找根节点的过程中,记录这条路径上的所有信息,…...
react学习笔记3——基于React脚手架
React路由 相关理解 SPA的理解 单页Web应用(single page web application,SPA)。整个应用只有一个完整的页面。点击页面中的链接不会刷新页面,只会做页面的局部更新。数据都需要通过ajax请求获取, 并在前端异步展现。 路由的理…...
C#中的LINQ:简化数据查询与操作
引言 在现代软件开发中,处理和操作数据是不可避免的任务。无论是从数据库读取信息,还是对内存中的集合进行筛选、排序等操作,开发者都需要一种高效且易于使用的方法。C#中的LINQ(Language Integrated Query)正是为此而…...
OkHttp3.X 工具类封装:链式调用,支持HTTPS、重试、文件上传【内含常用设计模式设计示例】
OkHttp3.X 工具类封装:链式调用,支持HTTPS、重试、文件上传 基于OkHttp3.X封装,提供链式调用API,简化GET/POST请求,支持HTTPS、自动重试、文件上传等功能,提升开发效率。 在 Android 和 Java 开发中&#x…...
Unity SpriteEditor(精灵图片编辑器)
🏆 个人愚见,没事写写笔记 🏆《博客内容》:Unity3D开发内容 🏆🎉欢迎 👍点赞✍评论⭐收藏 🔎SpriteEditor: 精灵图片编辑器 📌用于编辑2D游戏开发中使用的Sp…...
雅思写作--70个高频表达
文章目录 1. learn new skills学生通过户外活动学到很多新技2. take immediate action to do各国采取有效行动以保护环境政府采取了必要行动以减少失业。你应该立即采取行动来解3. communication skills4. grow significantly5. have many advantages1. learn new skills “lea…...
Anaconda中配置Pyspark的Spark开发环境
Anaconda中配置Pyspark的Spark开发环境 目录 1.在控制台中测试ipython是否启动正常2.安装好Java3.安装Spark并配置环境变量4.PySpark配置5.修改spark\conf下的spark-env文件6.测试Pyspark是否安装成功 1.在控制台中测试ipython是否启动正常 anaconda正常安装 这里先检查ipyt…...
Spring 提供了多种依赖注入的方式
构造器注入(Constructor Injection) 构造器注入是通过类的构造函数来注入依赖项。这是 Spring 推荐的方式,因为它提供了不可变性和更好的可测试性。 import org.springframework.stereotype.Component;Component public class ServiceA {pub…...
面经-计算机网络——OSI七层模型与TCP/IP四层模型的对比详解
OSI七层模型与TCP/IP四层模型的对比详解 一、图示解析:分层封装结构 你提供的图清晰展示了网络通信中从应用层到物理层的封装过程,每一层都会对上层的数据加上自己的头部信息(Header): 应用层: 应用…...
网络安全知识问答微信小程序的设计与实现
网络安全知识问答微信小程序的设计与实现,说白了,就是搭建一款网络安全知识问答微信小程序,类似网络安全百科直通车。三步走。 需求沟通 进行需求沟通,此处省略1000字。 画草图 根据沟通的需求,进行整理,…...
Canvas特效实例:黑客帝国-字母矩阵(字母雨)
黑客帝国-字幕矩阵(字母雨) 效果预览代码实现思路解析遗留问题 效果预览 话不多说,我们直接上效果:当页面加载完成,屏幕上会落下如瀑布般的绿色字母流,不断向下滑动,仿佛进入了黑客帝国的数字世…...
「Mac畅玩AIGC与多模态11」开发篇07 - 使用自定义名言插件开发智能体应用
一、概述 本篇介绍如何在 macOS 环境下,通过编写自定义 OpenAPI Schema,将无需认证的名言服务接入 Dify 平台,并开发基于外部公共数据的智能体应用。本案例继续实践 GET 请求型 API 的实际调用技巧。 二、环境准备 1. 确认本地开发环境 macOS 系统Dify 平台已部署并可访问…...
快速上手非关系型数据库-MongoDB
简介 MongoDB 是一个基于文档的 NoSQL 数据库,由 MongoDB Inc. 开发。 NoSQL,指的是非关系型的数据库。NoSQL有时也称作Not Only SQL的缩写,是对不同于传统的关系型数据库的数据库管理系统的统称。 MongoDB 的设计理念是为了应对大数据量、…...
响应式布局,在飞帆平台中如此简单
这些控件都是可以自己动手去实现的。也可以将这些控件复制到自己名下进行修改。 响应式布局https://fvi.cn/782...
UN R79 关于车辆转向装置形式认证的统一规定(正文部分1)
UN R79法规是针对转向装置的型式认证法规,涉及A/B1/C类的横向控制辅助驾驶功能,对各功能的功能边界、性能要求、状态提示、故障警示以及型式认证要提交的信息做了规范,本文结合百度文心一言对法规进行翻译,并结合个人理解对部分内…...
深度学习系统学习系列【1】之基本知识
文章目录 说明基础知识人工智能、机器学习、深度学习的关系机器学习传统机器学习的缺陷选择深度学习的原因深度学习的关键问题深度学习的应用深度学习的加速硬件GPU环境搭建主流深度学习框架对比 说明 文章属于个人学习笔记内容,仅供学习和交流。内容参考深度学习原…...
python3GUI--视频监控管理平台 By:PyQt5(详细讲解)
文章目录 一.前言二.相关知识1.PyQt52.RTSP协议📌 简介:🧩 特点:📡 工作方式: 2. **RTMP(Real-Time Messaging Protocol)**📌 简介:&a…...
第十一届蓝桥杯 2020 C/C++组 既约分数
目录 题目: 题目描述: 题目链接: 思路: 核心思路: 思路详解: 代码: 代码详解: 题目: 题目描述: 题目链接: 既约分数 - 蓝桥云课 思路&a…...
如何让Steam下载速度解除封印?!
平时一直没注意到家里的路由器在偷懒。最近成功榨干家里的带宽,把平时一直20mb/s左右下载速度的路由器一番改造后成功steam下载速度稳定85Mb/s。平时一直都只发挥了他的1/3不到,真是太可惜了。 硬件 首先检查硬件,就千兆路由器而言…...
HOOK上瘾思维模型——AI与思维模型【88】
一、定义 HOOK上瘾思维模型是一种通过设计一系列的触发(Trigger)、行动(Action)、奖励(Reward)和投入(Investment)环节,来促使用户形成习惯并持续使用产品或服务的思维框…...
基于开源AI智能名片链动2+1模式S2B2C商城小程序的IP开发泡沫破局与价值重构研究
摘要:当前IP开发领域普遍存在"冒进式泡沫"现象,企业将初级IP包装为超级IP运营,导致资源错配与价值虚化。本文通过实证分析开源AI智能名片链动21模式S2B2C商城小程序的技术架构与商业逻辑,揭示其通过智能内容引擎、合规化…...
深⼊理解指针(8)
1.对上一篇的补充内容 typedef int* ptr_t #define PTR_T int* 这两种写法都是可以的 ptr_t p1, p2; //p1, p2 都是指针变量 PTR_T p3, p4; //p3 是指针变量, p4是整型变量 为什么p3 是指针变量, p4是整型变量呢? 因为PTR_T 真的被改为了 int* 在编译器中…...
【iview】icon样式
A. 工程中引入样式文件 iview源码工程中的example工程中如何引入iview样式 image.png 自定义工程中如何引入iview样式 一般在src/main.js中引入(在index.html中也可以,当然app.vue中也可以) import "iview/dist/styles/iview.css"B…...
【计算机视觉】三维视觉:Nerfstudio:模块化神经辐射场框架的技术突破与实战指南
深度解析Nerfstudio:模块化神经辐射场框架的技术突破与实战指南 技术架构与核心创新系统架构设计关键技术特性 环境配置与安装指南硬件要求全平台安装流程 实战全流程解析1. 数据采集与预处理2. 模型训练与优化3. 可视化与导出 核心技术深度解析1. 混合表示网络2. 渐…...
第二章 OpenCV篇-图像阈值的综合操作-Python
目录 一.图像阈值 二.图像平滑 1.均值滤波 2.方框滤波 3.高斯滤波 4.中值滤波 5.双边滤波 此章节主要讲解:图像阈值、图像平滑处理、均值滤波、方框滤波、高斯滤波、中值滤波、双边滤波。 这里先讲作者使用matplotlib模块出现错误的解决方法。 首先作者在这…...
WPF处理大规模激光数据计算与安全传输处理
WPF大规模激光数据处理与安全传输系统设计方案 一、系统架构设计 1. 整体架构 ┌─────────────────────────────────────────────┐ │ WPF客户端应用 │ ├───────────────┬…...
vue 常见ui库对比(element、ant、antV等)
Element UI 1. 简介 Element UI 是一个基于 Vue 2 和 Vue 3 的企业级 UI 组件库,提供了丰富的组件和主题定制功能。官方网站:Element UI 2. 主要特点 丰富的组件:包括表单、表格、布局、导航、弹窗等多种组件。主题定制:支持主…...
【c++】【STL】stack详解
目录 stack类的作用什么是容器适配器stack的接口构造函数emptysizetoppushpopswap关系运算符重载 stack类的实现 stack类的作用 stack是stl库提供的一种容器适配器,也就是我们数据结构中学到的栈,是非常常用的数据结构,特点是遵循LIFO&#…...
单片机-89C51部分:12 pwm 呼吸灯 直流电机
飞书文档https://x509p6c8to.feishu.cn/wiki/JkzfwSoFBiUKc4kh8IoccTfyndg 一、什么是PWM? PWM是脉冲宽度调制的缩写,它是一种通过调整脉冲信号的高电平和低电平时间比例来控制电路输出的技术。简单来说,PWM是一种控制电子设备输出电压或电…...
WPF实现数据库操作与日志记录
1. 数据库操作实现 1.1 数据库连接基类 public abstract class DatabaseBase : IDisposable {protected string ConnectionString { get; }protected IDbConnection Connection { get; private set; }protected DatabaseBase(string connectionString){ConnectionString = co…...
用spring-boot-maven-plugin打包成单个jar有哪些缺点优化方案
Spring Boot 的 Fat JAR(通过 spring-boot-maven-plugin 打包)虽然简化了部署,但也存在一些潜在缺点,需根据场景权衡: 1. 启动速度较慢 原因: Fat JAR 需要在启动时解压并加载所有依赖的 JAR 文件到类路径…...
Spring Boot 使用 WebMagic 爬虫框架入门
1. 创建 Spring Boot 项目 使用 Spring Initializr 创建一个 Spring Boot 项目,选择需要的依赖,如 Spring Web 等。 2. 添加 WebMagic 依赖 在项目的 pom.xml 文件中添加 WebMagic 的核心和扩展依赖: <dependency><groupId>u…...