文章目录
- 一、实验目的
- 二、 实验内容
- 三、 实验原理
- 1、信号
- 3.1.1 信号的基本概念
- 3.1.2、信号的发送
- 2、所涉及的系统函数调用
- 3.2.1 fork()
- 3.2.2 kill( )
- This is my question.
- 3.2.3 signal( )
- 代码例子
- 3.2.4 getpid()
- 3.2.5 wait()
- 四、 实验内容
- 五、程序代码
- 运行结果
- 六、实验总结及心得体会:
- 心得:
- 思考题:
- 每天进步一点点 笔记仅供自学,用来回看复习,不一定适合你,如有错误请指出。
一、实验目的
1、了解什么是信号
2、了解和熟悉 LINUX 支持的信号量机制
3、熟悉 LINUX 系统中进程之间软中断通信的基本原理
二、 实验内容
1、根据 4.1 程序流程图,设计程序。用 fork( )
创建两个子进程,再用系统调用 signal( )
让父进程捕捉键盘上来的中断信号(即按^c 键
);
捕捉到中断信号后,父进程用系统调用 kill( )
向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
Child process1 is killed by parent!
Child process2 is killed by parent!
父进程等待两个子进程终止后,输出如下的信息后终止:
Parent process is killed!
2、分析利用软中断通信实现进程同步的机理
三、 实验原理
1、信号
3.1.1 信号的基本概念
每个信号都对应一个正整数常量(称为 signal number,即信号编号。
-
定义在系统头文件
<signal.h>
中),代表同一用户的各个进程之间传送事先约定的信息的类型,用于通知某进程发生了某异常事件。 -
每个进程在运行时,都要通过信号机制来检查是否有信号到达。
– 若有,便中断正在执行的程序,转向与该信号相对应的处理程序,以完成对该事件的处理;
– 处理结束后再返回到原来的断点继续执行。 -
实质上,信号机制是对中断机制的一种模拟,故在早期的 UNIX 版本中又把它称为软中断。
信号与中断的相似点:
- 采用了相同的异步通信方式;
- 当检测出有信号或中断请求时,都暂停正在执行的程序而转去执行相应的处理程序;
- 都在处理完毕后返回到原来的断点;
- 对信号或中断都可进行屏蔽。
信号与中断的区别:
- 中断有优先级,而信号没有优先级,所有的信号都是平等的;
- 信号处理程序是在用户态下运行的,而中断处理程序是在核心态下运行;
- 中断响应是及时的,而信号响应通常都有较大的时间延迟。
信号机制具有以下三方面的功能:
- 发送信号。发送信号的程序用系统调用 kill( )实现;
- 预置对信号的处理方式。接收信号的程序用 signal( )来实现对处理方式的预置;
- 收受信号的进程按事先的规定完成对相应事件的处理。
3.1.2、信号的发送
信号是操作系统用于通知进程的特殊事件。信号可以由系统内核发送,也可以由其他进程或用户发送。
- 当系统内核发送信号时,通常是用于报告某些错误或异常情况,例如内存访问越界、无效指令等。
- 当其他进程或用户发送信号时,通常是用于控制其他进程的行为。例如,使用
kill
命令可以向其他进程发送信号,控制其终止、暂停或继续执行。 - 在 C/C++ 中,可以使用 kill 函数来发送信号。
进程用 kill( )向一个进程或一组进程发送一个信号。
2、所涉及的系统函数调用
3.2.1 fork()
系统调用格式: pid_t fork( void);
#include <sys/types.h>
#include <unistd.h>
pid_t 是一个宏定义,其实质是 int 被定义在#include<sys/types.h>中
功能:创建子进程
返回值: 若成功调用一次则返回两个值
子进程
返回0
父进程
返回子进程 ID
否则,出错
返回-1
.
函数说明:
- 一个现有进程可以调用 fork 函数创建一个新进程。由 fork 创建的新进程被称为子进程(child process)。
- fork 函数被
调用一次,返回两次
。两次返回的唯一区别是子进程中返回 0 值而父进程中返回子进程 ID。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。
注意,
- 子进程持有的是上述存储空间的 “副本”,这意味着父子进程间不共享这些存储空间。
- linux 将复制父进程的地址空间内容给子进程,因此,子进程有了独立的地址空间。
3.2.2 kill( )
系统调用格式: int kill(pid,sig)
#include <sys/types.h>
#include <signal.h>
参数定义 int pid,sig;
其中,pid
是一个或一组进程的标识符,参数sig
是要发送的软中断信号。
(1)pid>0
时,核心将信号发送给进程 pid
。
(2)pid=0
时,核心将信号发送给与发送进程同组的所有进程
。
(3)pid=-1
时,核心将信号发送给所有用户标识符真正等于发送进程的有效用户标识号的进程。
This is my question.
当 pid 参数的值等于 -1 时,核心将信号发送给所有用户标识符真正等于发送进程的有效用户标识号的进程。这句话什么意思?
- 核心将信号发送给具有与发送进程相同的有效用户标识符的所有进程。
- 有效用户标识符是指系统分配给用户的唯一标识符,用于标识用户身份。
例如
,如果你的有效用户标识符是 1000,那么当你使用 kill(-1, sig) 发送信号时,核心会将信号发送给所有具有有效用户标识符 1000 的进程。
3.2.3 signal( )
预置对信号的处理方式,允许调用进程控制软中断信号。
系统调用格式: signal(sig,function)
#include <signal.h>
参数定义
signal(sig,function)
- int sig;
- void (*func) ( )
其中
sig
用于指定信号的类型,sig
为 0 则表示没有收到任何信号,
余者如下表:
function
:在该进程中的一个函数地址,在核心返回用户态时,它以软中断信号的序号作为参数调用该函数,对除了信号 SIGKILL、SIGTRAP 和SIGPWR 以外的信号,核心自动地重新设置软中断信号处理程序的值为 SIG_DFL,一个进程不能捕获 SIGKILL 信号。
function 的解释如下:
(1)function=1 时,进程对 sig 类信号不予理睬,亦即屏蔽了该类信号;
(2)function=0 时,缺省值,进程在收到 sig 信号后应终止自己;
(3)function 为非 0,非 1 类整数时,function 的值即作为信号处理程序的指针。
代码例子
#include <stdio.h>
#include <signal.h>
// 信号处理程序
void sigint_handler(int signum)
{
printf("收到 SIGINT 信号!\n");
}
int main(int argc, char*argv[])
{
// 设置 SIGINT 信号的处理程序
signal(SIGINT, sigint_handler);
return 0;
}
在上面的程序中,当收到
SIGINT
信号时,程序会调用sigint_handler()
函数。你可以在这个函数内部执行任何你希望在收到信号时执行的操作。
3.2.4 getpid()
pid_t getpid(void)
#include <sys/types.h>
#include <unistd.h>
功能:获取自己的进程 ID 号
参数:无
返回值:本进程的 ID 号
3.2.5 wait()
pid_t wait(int *wstatus);
#include <sys/wait.h>
功能:等待进程状态变化
四、 实验内容
程序所需头文件如下:
#include <stdio.h> //stdin, stdout, stderr,scanf(),printf()
#include <stdlib.h> //exit()
#include <signal.h> //signal()
#include <unistd.h> //getpid()
#include <sys/wait.h> //wait()
五、程序代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#define SIG17 17
#define SIG16 17
#define SIG2 17
//定义全局变量
pid_t child_pid1,child_pid2;
/*信号中断函数*/
void sigint_handler(int signum)
{
// 向两个子进程发送信号
kill(child_pid1, SIGINT);
kill(child_pid2, SIGINT);
}
/*子程序中断函数*/
void child_handler(int signum)
{
printf("child process %d is killed by parent!\n", getpid());
exit(0);
}
/*主函数*/
int main(int argc, char *argv[])
{
// 创建两个子进程
child_pid1 = fork();
if (child_pid1 == 0)
{
// 这是第一个子进程
signal(SIGINT, child_handler);
while (1)
{
sleep(1);
}
}
child_pid2 = fork();
if (child_pid2 == 0)
{
// 这是第二个子进程
signal(SIGINT, child_handler);
while (1)
{
sleep(1);
}
}
// 输出父进程和子进程的 PID
printf("parent PID:%d\n", getpid());
printf("child1 PID:%d\n", child_pid1);
printf("child2 PID:%d\n", child_pid2);
//使用信号类型 2 暂停父进程
printf("stop PID:%d by signal 2\n", getpid());
kill(getpid(), SIG2);
// 使用信号类型 17 暂停子进程 2
printf("stop PID:%d by signal 17\n", child_pid2);
kill(child_pid2, SIG16);
// 使用信号类型 16 暂停子进程 1
printf("stop PID:%d by signal 16\n", child_pid1);
kill(child_pid1, SIG17);
// 使用信号类型 2 暂停两个子进程
printf("stop PID:%d by signal 2\n", child_pid1);
printf("stop PID:%d by signal 2\n", child_pid2);
kill(child_pid2, SIG2);
kill(child_pid1, SIG2);
// 为父进程设置信号处理程序
signal(SIGINT, sigint_handler);
// 等待两个子进程终止
waitpid(child_pid1, NULL, 0);
waitpid(child_pid2, NULL, 0);
printf("Parent peocess is killed!\n");
return 0;
}
运行结果
六、实验总结及心得体会:
心得:
- 在这个实验中,我使用了 fork()函数来创建子进程,并使用 signal()函数来处理信号,使用 kill()函数来发送信号。还使用了 wait()函数来等待子进程的终止,并使用 exit()函数来退出进程。
- 通过这个实验,我更好地了解进程的工作原理,包括进程的创建、信号的发送和接收以及进程的终止。这对于我们编写多进程程序是非常有帮助的
思考题:
- 当首次调用新创建进程 fork()时,其入口在哪里?
答:当首次调用 fork() 函数创建新进程时,该进程的入口在函数调用处。也就是说,新进程将从 fork() 函数调用的位置开始执行,而不是从函数的开头开始执行。
- 程序段中调用 wait(0)起什么作用?
答:wait() 函数是一个系统调用,用于让当前进程等待其子进程的终止。它的第一个参数可以是一个指向某个整型变量的指针,用于存储子进程的终止状态。如果不希望父进程获取子进程的终止状态,可以将参数设置为 0。
当调用 wait(0) 时,当前进程会等待其任意子进程的终止,然后返回该子进程的终止状态。
- 程序段中每个进程退出时都用了语句 exit(0),为什么?
答: exit(0) 函数用于终止当前进程,并返回一个状态码。这里的状态码是 0,表示进程正常结束。