When a Qt app using QSerialPort
experiences a non-clean shutdown (e.g. due to receiving and not handling SIGINT
), how is the file descriptor of the serial port affected?
After running an app that opens a QSerialPort
on /dev/ttyS0
, then quitting with Ctl-C
, I am finding that cat < /dev/ttyS0
returns instantly (without printing anything) rather than waiting for data (as it usually does).
I would expect that if this is due to an open file handle left hanging around, it would show up in the output of lsof
, but lsof | grep ttyS0
returns nothing. (I'm not really sure how else to search for handles on a particular file descriptor.)
I realize this is a bit of an XY problem, since I could avoid the problem entirely by rewriting my app to properly handle SIGINT
, but I'd like to have a deeper understanding of what's going on here and if there's a way to recover the serial port when it's in this state.
EDIT: As requested, here is the output of strace cat /dev/ttyS0
:
execve("/bin/cat", ["cat", "/dev/ttyS0"], [/* 17 vars */]) = 0
brk(0) = 0x91ce000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb76fb000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=72063, ...}) = 0
mmap2(NULL, 72063, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb76e9000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/i686/cmov/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\240o\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1446056, ...}) = 0
mmap2(NULL, 1460600, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7584000
mmap2(0xb76e3000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15e) = 0xb76e3000
mmap2(0xb76e6000, 10616, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb76e6000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7583000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb75838d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb76e3000, 8192, PROT_READ) = 0
mprotect(0x8054000, 4096, PROT_READ) = 0
mprotect(0xb771a000, 4096, PROT_READ) = 0
munmap(0xb76e9000, 72063) = 0
brk(0) = 0x91ce000
brk(0x91ef000) = 0x91ef000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=1534672, ...}) = 0
mmap2(NULL, 1534672, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb740c000
close(3) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
open("/dev/ttyS0", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFCHR|S_ISVTX|0660, st_rdev=makedev(4, 64), ...}) = 0
fadvise64_64(3, 0, 0, POSIX_FADV_SEQUENTIAL) = 0
read(3, "", 32768) = 0
close(3) = 0
close(1) = 0
close(2) = 0
exit_group(0) = ?
And here is the output of stty -a -F /dev/ttyS0
:
speed 57600 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 0; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread clocal -crtscts
-ignbrk -brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8
-opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon -iexten -echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke
Under POSIX, terminal devices (that is, serial ports and pseudoterminals) have a whole bunch of settings which enable the computer to speak the multitude of variations on the basic RS-232 protocol that exist or have existed. A great deal of this API was designed back in the days when dinosaurs and teletypewriters (hence "tty") ruled the earth, and we wouldn't do it over again the same way, but we're stuck with it now.
The terminal settings are persistent; once one program sets them, they stay that way until another program changes them. The command-line utility stty
can print or change these settings; stty sane
resets them all to "reasonable" defaults; stty -a
prints them all out.
Here are all the terminal settings that differ between what stty sane
applies on my computer, and what QSerialPort
did to your serial port. (The ones that are just a cryptic label, possibly with a dash in front, are boolean flags; the leading dash means "off", no leading dash means "on".)
QSerialPort stty sane
---------------- ----------------
speed 57600 baud speed 38400 baud
min = 0 min = 1
clocal -clocal
-brkint brkint
ignpar -ignpar
-icrnl icrnl
-ixon ixon
-imaxbel imaxbel
-opost opost
-isig isig
-icanon icanon
-iexten iexten
-echo echo
Many of the QSerialPort
settings are abnormal in the sense that a line- or file-oriented program hooked up to a serial port in this state will misbehave. (However, they are perfectly appropriate for a program that knows it is talking to a serial port and is prepared to deal with the consequences of having turned these particular knobs; presumably the authors of QSerialPort
knew what they were doing.) The one that is causing cat
to quit immediately is min = 0
, which (together with the default time = 0
) means "read()
should return zero bytes if there is no input pending." Under normal circumstances, zero bytes returned from read()
means end of file, so cat
quits immediately because it thinks it's been handed an empty file. (This mode may well have been invented years before O_NONBLOCK
.)
stty sane
is the "way to recover the serial port" that you were looking for. The documentation does not say either way, but if QSerialPort::close()
called from your SIGINT
handler does not restore the terminal to its original state, I would consider that a bug in Qt. You should also do this upon receipt of SIGHUP
, SIGQUIT
, SIGABRT
, SIGTERM
, and arguably SIGTSTP
, SIGTTIN
, SIGTTOU
as well (but that's more complicated, because those aren't fatal). Make sure to restore the default handler and re-raise the signal afterward, so that the exit status is correct.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With