Skip to content

Enhance QEMU interactive shell and exit flow#383

Open
StevenChen1997 wants to merge 2 commits into
sysprog21:masterfrom
StevenChen1997:qemu_support_jobctrl
Open

Enhance QEMU interactive shell and exit flow#383
StevenChen1997 wants to merge 2 commits into
sysprog21:masterfrom
StevenChen1997:qemu_support_jobctrl

Conversation

@StevenChen1997

@StevenChen1997 StevenChen1997 commented Jul 15, 2026

Copy link
Copy Markdown

Enable Job Control in QEMU Interactive Shell

In LKMPG chapter 27, I was suffering on testing module blkram under QEMU interactive shell.
When I execute cat /dev/blkram0, I found that I cannot use Ctrl-C / Ctrl-Z to exit the command.
The QEMU shell hung here unless using Ctrl-A and X to terminate QEMU.

After confimation, I have found that job control is turned off due to constraint of script init.
This situation caused Ctrl-C / Ctrl-Z unavailable in legacy implementation.

================================================
  lkmpg QEMU guest
  Project:  /mnt/lkmpg/
  Modules:  /mnt/lkmpg/examples/*.ko
  Example:  insmod /mnt/lkmpg/examples/hello-1.ko
            dmesg | tail
            rmmod hello_1
  Exit:     poweroff
================================================
/bin/sh: can't access tty; job control turned off
lkmpg:~#

In proposed solution, I start a new session with setsid and get rid of impact by script init.
Then, we set current controlling terminal to ttyS0 and use cttyhack to run up /bin/sh.
The job control of shell is enabled under current implementation.

Exit flow enhancement

During tesing on QEMU shell, I found that kernel panic is taken place when we input exit.
Under this situation, we should use Ctrl-A and X to terminate the QEMU.
Refer to the output log, the root cause is trying to kill init process.

lkmpg:~# exit
[   71.431267] Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000
[   71.432071] CPU: 1 UID: 0 PID: 1 Comm: sh Not tainted 6.12.6 #3
[   71.432246] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   71.432680] Call Trace:
[   71.433979]  <TASK>
[   71.434272]  panic+0x326/0x380
...
[   71.438167]  </TASK>
[   71.439001] Kernel Offset: disabled
[   71.439284] ---[ end Kernel panic - not syncing: Attempted to kill init! exitcode=0x00000000 ]---

Refer to ./devtools/test-modules.sh, it used poweroff -f to exit QEMU shell without kernel panic.
Hence, I also imported this method into our init script. After testing, the QEMU shell can exit without
kernel panic.

================================================
  lkmpg QEMU guest
  Project:  /mnt/lkmpg/
  Modules:  /mnt/lkmpg/examples/*.ko
  Example:  insmod /mnt/lkmpg/examples/hello-1.ko
            dmesg | tail
            rmmod hello_1
  Exit:     exit (or Ctrl-D)
            Will execute poweroff -f
================================================
lkmpg:~# exit
[    5.367168] ACPI: PM: Preparing to enter system sleep state S5
[    5.368402] reboot: Power down

Summary by cubic

Enables job control in the QEMU guest shell and makes exit clean to avoid kernel panic. Also removes a redundant TTY setup in init.

  • New Features

    • Launch the shell with setsid /bin/cttyhack /bin/sh so Ctrl-C/Ctrl-Z/fg work.
    • Clearer prompt via PS1='lkmpg:\w# ', and the boot banner now documents “Exit: exit (or Ctrl-D). Will execute poweroff -f”.
  • Bug Fixes

    • After the shell exits, run poweroff -f so QEMU powers down instead of panicking on exit/Ctrl-D`.

Written for commit cfb85b3. Summary will update on new commits.

Review in cubic

The legacy QEMU shell does not support job control. It makes related commnand
disabled, such as Ctrl-C/Ctrl-Z/fg. I use setsid and cttyhack to launch a new
session with ttyS0. The job control commands are enabled after modification.

In addition, I also found that kernel panic takes place when we exit the QEMU
shell. The root cause of panic is init process killed. I have added command
'poweroff -f' in this commit. It will be executed when QEMU shell exit by
shell command 'exit' or Ctrl-D. The command can correctly terminate QEMU
shell without kernel panic after modification.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 1 file

Re-trigger cubic

Comment thread devtools/initramfs/init
# Launch interactive shell with designated PS1 format
export PS1='lkmpg:\w# '
exec /bin/sh
setsid /bin/cttyhack /bin/sh

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explain this.

@StevenChen1997 StevenChen1997 Jul 16, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Conclusion (Before detailed description)

  • Since cttyhack will search active tty device automatically, we can remove redundant command:

    • exec 0</dev/ttyS0 ; exec 1>/dev/ttyS0 ; exec 2>&1
    • Please refer to commit cfb85b3
  • An alternative solution to assign tty device for new session without cttyhack is:

    • setsid -c /bin/sh </dev/ttyS0 >/dev/ttyS0 2>&1.
  • Please feel free to discuss on better solution


Recap script init

After confimation, I have found that job control is turned off due to constraint of script init.
This situation caused Ctrl-C / Ctrl-Z unavailable in legacy implementation.

About the command setsid /bin/cttyhack /bin/sh

  • setsid aims to run up a program in a new session. (Busybox Implementation Reference).

    • In init (pid == 1) , the script will call fork() to create a child process for executing setsid /bin/cttyhack /bin/sh.
    • Meanwhile, script init is blocked here as a daemon process until child process returned.
      Hence, kernel panic will not be triggered here because init is still alive.
    • The setsid will set child process (pid != 1) to process group leader, which represent a brand new session.
      Then execute /bin/cttyhack /bin/sh by execvp().
  • The /bin/cttyhack is an user program provided by busybox.

    • Since the new session is not related to any tty device, we use cttyhack to automatically find active tty device for current session.

    • The cttyhack will search /sys/class/tty/console/active to figure out active tty devices.
      If sysfs is not mounted, cttyhack will use ioctl() to lookup tty devices.
      If ioctl() failed, use default tty as final tty device. (e.g. /dev/tty0)

    • If there are additional program attached in argv (e.g. /bin/sh in this command) of cttyhack,
      cttyhack will open target tty device and redirect stdin, stdout and stderr to target tty device with dup2().
      It also closes unnecessary fd and steals tty device from other shell (if any).

    • Finally, cttyhack use execve() to launch designated program (i.e. /bin/sh in this command).

  • /bin/sh runs up the interactive shell with job control enabled.

  • This command refers to cttyhack manual:

    • Refer to 3rd command - Starting interactive shell from boot shell script
lkmpg:~# cttyhack --help
BusyBox v1.37.0 (2026-07-05 18:07:17 CST) multi-call binary.

Usage: cttyhack [PROG ARGS]

Give PROG a controlling tty if possible.
Example for /etc/inittab (for busybox init):
        ::respawn:/bin/cttyhack /bin/sh
Giving controlling tty to shell running with PID 1:
        $ exec cttyhack sh
Starting interactive shell from boot shell script:
        setsid cttyhack sh
lkmpg:~#

Recap QEMU configuration

  • In devtools/boot.sh, we have assigned active console in kernel command line.
    Hence, we can obtain active tty via reading /sys/class/tty/console/active.
# Kernel command line
KCMD="console=ttyS0 loglevel=7 nokaslr"
if [ -n "$TEST_CMD" ]; then
    # Base64-encode the command to survive kernel cmdline word-splitting.
    # Strip newlines portably so this works with both GNU and BSD base64.
    # The init script decodes it before execution.
    KCMD="$KCMD lkmpg.cmd64=$(printf '%s' "$TEST_CMD" | base64_nowrap)"
fi
  • Read /sys/class/tty/console/active in QEMU guest shell:
lkmpg:~# cat  /sys/class/tty/console/active
ttyS0

Other reference:

1. Remove redundant tty device setting
2. Add description nearby command for launching shell
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants