ctrl+c终止信号传递问题

前提

NovaShell主线版本中exec命令未禁用终端raw模式会导致进程无法接收信号,此处手动修改NovaShell代码,在exec前禁用raw模式

问题发现

禁用raw模式后,使用测试程序测试是否能正常接收SIGINT信号,代码如下:

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

// 信号处理函数
void handle_signal(int signal)
{
    if (signal == SIGINT)
    {
        printf("Caught SIGINT (Ctrl+C). Exiting gracefully...\n");
        // 你可以在这里执行清理操作
        exit(0); // 终止程序
    }
}

int main()
{
    // 注册信号处理函数
    signal(SIGINT, handle_signal);
    printf("program pid: %d.\n", getpid());

    // 模拟一个长时间运行的进程
    while (1)
    {
        printf("Running... Press Ctrl+C to stop.\n");
        sleep(5);
    }

    return 0;
}

发现依旧无法终止进程

调试过程

在内核的ProcessManager::exit()方法中打印日志输出被终止进程的pid:

在NovaShell中打印shell进程的pid:

在测试程序中打印pid:

运行结果如下:


shell pid: 6
program pid: 7
pid to exit: 0
pid对比后发现都不符合,因此猜测:SIGINT信号没有正确地传到进程中,导致ctrl+c无法终止进程
根据日志中的Pid(0)猜测信号传递到init程序DragonReach中?
@longjin @GnoCiYeH

这里可能有助于定位问题:
https://code.dragonos.org.cn/xref/DragonOS/kernel/src/driver/tty/tty_ldisc/ntty.rs?r=a1fc824fcc3bd4c2e267d8c0e9e3866fc7310bba&mo=24007&fi=760#403

当tty的ISIG置位时,内核将会通过self.recv_sig_char来处理信号发送的事情。
最终会跳转到这里:https://code.dragonos.org.cn/xref/DragonOS/kernel/src/driver/tty/tty_ldisc/ntty.rs?r=a1fc824fcc3bd4c2e267d8c0e9e3866fc7310bba&mo=24007&fi=760#781
我怀疑是kill pg,但是pgid是0.

当前好像我们内核没实现进程组。我在想,对于ctrl c这个事情,能否简单实现对pgid的处理:

  • execve时,进程把pgid设置为自己的pid
  • 进程创建线程时,子线程的pgid设置为跟自己的pid一样。(这个应该是不符合linux语义的)

@Samuka007 我认为这个发送信号的事情,也许是actix web跑不起来的原因之一。因为当前没有正确处理信号接收的对象(发给进程组)。

在你说的地方打印日志,发现的确会跳转到这里,并将pgid当成pid调用kill

于是我希望通过在应用程序中手动设置pgid让信号传递到正确的进程,但发现setpgid系统调用未实现
我继续寻找其他方法试图修改pgid,然后发现这个pgid会在两个地方设置:

  1. TtyDevice::open()方法中调用proc_set_tty方法,会设置pgid为当前进程pid
    tty_job_control.rs (revision 52bcb59e9286def2b66d766f6bf6f46745795ec8) - OpenGrok cross reference for /DragonOS/kernel/src/driver/tty/tty_job_control.rs
  2. ioctl传递参数为TIOCSPGRP,最终进入job_ctrl_ioctl方法
    tty_job_control.rs (revision 52bcb59e9286def2b66d766f6bf6f46745795ec8) - OpenGrok cross reference for /DragonOS/kernel/src/driver/tty/tty_job_control.rs

于是尝试在NovaShell中使用ioctl设置前台进程组,无效

打印日志发现ioctl调用成功,但没有走到设置pgid这一步

猜测是由于ctrl.session.unwrap() != current.pid()这一判断条件导致,因为tty初始化时将session设置为当时的pid(为1),导致NovaShell进程(pid为6)调用ioctl时条件不符导致未能成功设置
我感觉这样的话是不是tty也有点问题,记录的前台进程会一直为1
@longjin @GnoCiYeH

1 个赞

@GnoCiYeH 这块麻烦关注下~

这个地方我怀疑是不是需要在dragonreach中调用ioctl来设置前台进程?你可以试试把init程序换为NovaShell启动,再查看是否能切换前台进程。后续若有问题再讨论。

@MemoryShore


init程序换成NovaShell,可以切换前台进程了
这样修改一下dragonreach,应该就能初步实现ctrl+c终止信号的功能了
@GnoCiYeH @longjin

我查了关于会话和进程组的资料,发现shell应该是一个会话的控制进程,像这样:
image
所以正确的做法应该是在shell进程中开启新的会话并设置前台进程,而不是在init程序中设置前台进程

进一步查找得知使用ioctl() + TIOCSCTTY命令可以设置tty的会话id,参考函数:tiocsctty

但是这样需要进程支持sid并实现setsid系统调用

看来前两天我跟你说的那个session还是得实现啊哈哈 @GnoCiYeH

@MemoryShore 要不看看有没有简单的实现方式?

我简单实现了一下,具体见pr: