Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

QSerialPort effect on `/dev/ttyS*` after process end?

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
like image 604
Kyle Strand Avatar asked Oct 19 '22 16:10

Kyle Strand


1 Answers

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.

like image 59
zwol Avatar answered Oct 22 '22 10:10

zwol