日期:2025.1.26(凌晨)
学习内容:
- Linux的信号
- 进程终止
- 调用可执行程序
- 创建进程
Linux的信号
首先要知道,我们是可以向进程发送信号的。
要么是直接键盘上发出命令(ctrl + c),或者是利用kill命令。
kill命令
主要是两个命令:kill和killall命令。
kill命令是要加上pid,而killall命令是要加上进程的名字。
选项默认的是发出一个SIGTERM(15)命令,用于终止。
-9是强制终止。
而在信号的处理上,我们可以修改原本信号的处理器,使得处理信号自定义。
#include <stdio.h>
#include <signal.h>
#include <unistd.h>// 定义一个信号处理器函数
void handle_signal(int sig) {printf("Received signal %d\n", sig);//signal(sig, SIG_DFL); // SIG_DFL恢复信号的默认处理方式
}int main() {// 设置 SIGINT (通常是 Ctrl+C) 的处理器为 handle_signalsignal(SIGINT, handle_signal);// 等待信号的到来while (1) {printf("Running...\n");}return 0;
}
上述代码中,修改了\(SIGINT\)的信号处理,变成了输出一段话。
在这里中,使用了回调函数。就像这个signal函数中,第二个参数是函数指针,像这种参数是函数指针的,一般就称为回调函数。而这个函数指针对应的函数的参数,是自动接收了信号对应的数字(因为信号SIGINT就是一个宏,其实是一个数字)。
这个signal函数是用来修改信号的处理方式的。可以理解为,当执行到这个函数时,会将SIGINT的处理变成了调用这个函数。如果将注释的内容去掉的话,那么执行第一次之后,处理方式会变成了默认处理方式。
另外还有一些宏定义是:
- SIG_IGN(忽略该信号)
- SIGSHLD(杀死子进程)
还有是killall -0,这个选项可以判断出进程是否还存活。
另外关于函数指针:
#include <iostream>
#include <string>
using namespace std;int myTest(int a, string b)
{cout << "a = " << a << " b = " << b << endl;return 123;
}using func = int(*)(int, string);int main()
{func f = myTest;int a=f(10, "hello");int b = (*f)(10, "hello");cout << a << endl;cout << b << endl;return 0;
}
以上代码中两个调用函数的方式,没有区别。
进程终止
主要是关于exit函数的应用。包括(_exit,_Exit,atexit)函数。
exit这些函数的参数都代表着进程结束时的状态,0代表正常,非0代表不正常。
要注意的点是,析构函数在exit函数使用了之后,只会触发全局变量的析构函数,而不会触发局部变量的析构函数。(插一句,正常return是会触发局部变量的析构,main函数的return会触发全局变量的析构)。而_exit,_Exit函数是都不触发。
atexit函数的参数是函数指针,最多使用32次。
代表当程序exit了时候,会去执行之前使用过的(即放在参数里的)函数。
#include <stdio.h>
#include <stdlib.h>// 定义一个清理函数
void cleanup1() {printf("Cleanup 1: Releasing resources...\n");
}void cleanup2() {printf("Cleanup 2: Saving state...\n");
}int main() {// 注册清理函数atexit(cleanup1);atexit(cleanup2);exit(0);printf("Program is running...\n");// 正常退出return 0;
}
这里的输出是先输出cleanup2的内容,再输出1的内容。(倒着的)
调用可执行程序
主要是有system,execl,execv函数。后者两个很像,会简单概括。
system的参数是const char *,里面放的就是命令。
而execl函数的参数讲起来比较麻烦,直接上代码。
#include <unistd.h>
#include <stdio.h>int main() {// 使用 execl 执行 /bin/ls 命令execl("/bin/ls", "ls", "-l", NULL);// 如果 execl 成功,不会执行下面的代码printf("This line will not be executed if execl is successful.\n");return 0;
}
如图,第一个参数是命令的路径,第二个是命令本身的名称,当然放路径也是可以的,也就是说第一个参数和第二个参数一模一样,然后后面可以加上选项。结尾用0或者NULL。
要注意的是,使用了execl函数后,会开启一个新的进程,而这个进程会代替现在的进程,取代了数据段和堆栈。
execv就是把参数都放到数组里面。(但是第一个参数还是要写的)
int main() {char* argv[10];argv[0] = (char*)"/bin/ls";argv[1] = (char *)"-l";argv[2] = 0;execv("/bin/ls",argv);return 0;
}
创建进程
#include <unistd.h>
#include <stdio.h>
int main() {pid_t pid = getpid();pid_t ppid = getppid();return 0;
}
第一个是获取当前进程的pid,第二个是获取当前进程的父进程的pid。
类型是pid_t,宏定义,原本是int。
fork函数:
用来创建一个子进程的,返回值是有两个。
在当前进程的返回值是当前进程的pid,而在子进程的返回值是0。(根据这个可以判断出来当前是子进程还是进程)。
在使用了这个函数之后,之后的部分就可以理解成会跑两遍。所以像文件ofstream类,要在调用fork函数之前就使用,这样就能够实现两个进程跑的内容是共有的而不是重叠的。
ofstream:
#include <fstream>
#include <iostream>int main() {// 创建一个 ofstream 对象std::ofstream outFile;// 打开文件outFile.open("example.txt");// 写入数据outFile << "Hello, World!" << std::endl;// 关闭文件outFile.close();return 0;
}
ifstream:
#include <fstream>
#include <iostream>
#include <string>int main() {std::ifstream inFile("example.txt");if (inFile.is_open()) {std::string line;while (getline(inFile, line)) {std::cout << line << std::endl;}inFile.close();} else {std::cerr << "Unable to open file" << std::endl;}return 0;
}
fstream:
std::fstream file("example.txt", std::ios::in | std::ios::out | std::ios::app);
if (file.is_open()) {file << "Appending text to file." << std::endl;file.seekg(0); // 移动到文件开头std::string line;while (getline(file, line)) {std::cout << line << std::endl;}file.close();
} else {std::cerr << "Unable to open file" << std::endl;
}