C write() doesn't send data until close(fd) is called

15,021

Solution 1

From the man page of write():

A successful return from write() does not make any guarantee that data has been committed to disk. In fact, on some buggy implementations, it does not even guarantee that space has successfully been reserved for the data. The only way to be sure is to call fsync(2) after you are done writing all your data.

You need to call fsync() on your file descriptor to ensure the data is actually committed.

Solution 2

The device is a tty device, so fsync isn't going to help, maybe not fflush either.

By default the device is working in canonical mode which means that data is packaged up into units of lines. You'll probably find that adding a cr/lf pair to your data will cause it to be sent.

You need to make sure canonical mode is off. Also, R's answer will be of use.

http://en.wikibooks.org/wiki/Serial_Programming/termios

Solution 3

first of all, no idea why you first set all termios fields to 0, and then later, without any modification to that 0 preceding it, decide to set the usual rs232 flags on the cflag. (rather than doing that without the OR directly, where you now set it to 0, above).

what you might like -instead- of setting all those flags is just cfmakeraw() the termios fields.

also, sync(); without any parameters (NOT fsync! ;) seems to send all pending output to ALL filedescriptors, not just block devices. also tcp sockets and rs232..

and also open() has an option O_SYNC (O_SYNC and O_ASYNC have confusing names but have nothing to do with the serial line protocol being clocked or not, the one immediately commits write()'s and the other generates a signal for trapping when input becomes available (kinda like irq based rs232 on dos ;)

setting O_SYNC in the open() might already fix your issue.

also 'by reading the data on the other end'... there are these things called 'leds' and 'resistors' which you can just connect to TXD and SEE the data ;) also there are things called 'rs232 breakout box' or a scope that can make it -directly visible- ;) much easier that way than 'guessing' which side is not behaving properly.

WARNING: DID NOT TEST CODE. it compiles. but i have all my ttyUSB0 cables in another building. but i think your main issue is O_SYNC anyway. setting all termios crap to 0 is pretty much the same as cfmakeraw()... also why set CREAD if you are gonna open it write only? (why open it write only rather than readwrite anyway? - and also with write only you won't have to be scared of it becoming a controlling tty (O_NOCTTY ;) so in the case of write only, that's not exactly needed either...

just noticed the %i (same for %d btw) formatter also triggers a type mismatch warning the ssize_t return value of write() so casted that to (int)

#include<termios.h>
#include<stdio.h>
#include<unistd.h>
#include<fcntl.h>

void main(){
int fd;
struct termios tty;
fd=-1;
while(fd<0){fd=open("/dev/ttyUSB0",O_WRONLY|O_NONBLOCK|O_NOCTTY|O_SYNC);sleep(1);};
cfmakeraw(&tty);
tty.c_cflag=CREAD|CRTSCTS|HUPCL|CS8;
cfsetospeed(&tty,B19200);
cfsetispeed(&tty,B19200);
tcsetattr(fd,TCSANOW,&tty);
printf("Write: %i\n",(int)write(fd,"HELLO",5));
sync();//if all else fails, also try without, O_SYNC should already fix that.
close(fd);
};

Solution 4

Output ports are often buffered, so that there's a greater or lesser gap between you writing to an output stream, and the content actually being sent to the disk, line, or whatever. This is generally for efficiency.

See fflush(3) to force the buffer to be committed to the output.

You might also be able to open the output descriptor in a way which makes it non-buffered, but using fflush to say 'that's it, I'm done', is probably better.

Share:
15,021
Kristina
Author by

Kristina

Updated on July 18, 2022

Comments

  • Kristina
    Kristina almost 2 years

    So I have this test code to send "HELLO" over a USB serial port:

    int fd;
    struct termios tty;
    
    if((fd = open("/dev/ttyUSB0", O_WRONLY|O_NONBLOCK|O_NOCTTY)) == -1){
    err(1, "Cannot open write on /dev/ttyUSB0");
    }
    
    tcgetattr(fd, &tty);
    tty.c_iflag = 0;
    tty.c_oflag = 0;
    tty.c_lflag = 0;
    tty.c_cflag = 0;
    tty.c_cc[VMIN] = 0;
    tty.c_cc[VTIME] = 0;
    cfsetospeed(&tty, B19200);
    cfsetispeed(&tty, B19200);
    tty.c_cflag |= CREAD|CRTSCTS|HUPCL|CS8;
    tcsetattr(fd, TCSANOW, &tty);
    
    printf("Write: %i\n", write(fd, "HELLO", 5));
    
    sleep(5);
    
    if(close(fd) != 0){
    warn("Could not close write fd");
    }
    

    The program executes fine and "HELLO" is sent but there is one problem. "HELLO" doesn't seem to be sent when the write() function is called, but rather when the file descriptor is closed. I added the sleep(5) line above to test out this theory and sure enough, "HELLO" is sent ~5 seconds after the program is executed. How can I get "HELLO" to be sent immediately after the write() command instead of on close()?

  • Alexandre C.
    Alexandre C. almost 14 years
    fflush needs a FILE*, here OP has a file descriptor (and probably would like not to use stdio)
  • Norman Gray
    Norman Gray almost 14 years
    Ooops, yes, fflush(3) is for FILE*, and as others have pointed out, fsync(2) is probably what you want if you do need to use FDs.
  • Kristina
    Kristina almost 14 years
    Adding "fsync(fd);" right after the write(fd) line returns -1: "Invalid argument".
  • R.. GitHub STOP HELPING ICE
    R.. GitHub STOP HELPING ICE almost 14 years
    Unrelated. The problem is that the terminal device is in line-buffered mode.
  • Norman Gray
    Norman Gray almost 14 years
    In fact, look at fcntl(2) on your platform. On some platforms, that will allow you to disable buffering for output, but that's not portable. If you want to give up on portability altogether, then the ioctls for your device will probably help with this, but that's getting desperate.
  • ShinTakezou
    ShinTakezou almost 14 years
    fdopen can be used; if this solves the OP problem and he has no reason why not to use file handler instead of file descriptor.
  • ShinTakezou
    ShinTakezou almost 14 years
    could not solve the problem for 2 reason: 1) does fflush care about files opened with open? maybe a fdopen is required; 2) notes from the man page: fflush() only flushes the user space buffers provided by the C library. To ensure that the data is physically stored on disk the kernel buffers must be flushed too, for example, with sync(2) or fsync(2)
  • ShinTakezou
    ShinTakezou almost 14 years
    fd -> fdopen -> fh; fh -> fileno -> fd... the problem is not having "FILE*" or fd (if compliance is for POSIX and not strictly C89/C99)
  • Kristina
    Kristina almost 14 years
    tcflush(fd, TCOFLUSH); returns 0 but doesn't help
  • Louis Marascio
    Louis Marascio almost 14 years
    Also, try transmitting a new line.
  • Kristina
    Kristina almost 14 years
    tcdrain(fd); returns 0 but doesn't help
  • Ranjith Subramaniam
    Ranjith Subramaniam over 6 years
    Can you please post your appropriate configuration here?