5

I have searched diligently (both within the S[O|F|U] network and elsewhere) and believe this to be an uncommon question. I am working with an Atmel AT91SAM9263-EK development board (ARM926EJ-S core, ARMv5 instruction set) running Debian Linux 2.6.28-4. I am writing using (I believe) the tty driver to talk to an RS-485 serial controller. I need to ensure that writes and reads are atomic. Several lines of source code (listed below the end of this post relative to the kernel source installation directory) either imply or implicitly state this.

Is there any way I can verify that writing/reading to/from this device is actually an atomic operation? Or, is the /dev/ttyXX device considered a FIFO and the argument ends there? It seems not enough to simply trust that the code is enforcing this claim it makes - as recently as February of this year freebsd was demonstrated to lack atomic writes for small lines. Yes I realize that freebsd is not exactly the same as Linux, but my point is that it doesn't hurt to be carefully sure. All I can think of is to keep sending data and look for a permutation - I was hoping for something a little more scientific and, ideally, deterministic. Unfortunately, I remember precisely nothing from my concurrent programming classes in the college days of yore. I would thoroughly appreciate a slap or a shove in the right direction. Thank you in advance should you choose to reply.

Kind regards,

Jayce


drivers/char/tty_io.c:1087

void tty_write_message(struct tty_struct *tty, char *msg)
{
    lock_kernel();
    if (tty) {
        mutex_lock(&tty->atomic_write_lock);
        if (tty->ops->write && !test_bit(TTY_CLOSING, &tty->flags))
            tty->ops->write(tty, msg, strlen(msg));
        tty_write_unlock(tty);
    }
    unlock_kernel();
    return;
}


arch/arm/include/asm/bitops.h:37

static inline void ____atomic_set_bit(unsigned int bit, volatile unsigned long *p)
{
    unsigned long flags;
    unsigned long mask = 1UL << (bit & 31);

    p += bit >> 5;

    raw_local_irq_save(flags);
    *p |= mask;
    raw_local_irq_restore(flags);
}


drivers/serial/serial_core.c:2376

static int
uart_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
    struct uart_state *state = tty->driver_data;
    struct uart_port *port;
    struct circ_buf *circ;
    unsigned long flags;
    int c, ret = 0;

    /*
     * This means you called this function _after_ the port was
     * closed.  No cookie for you.
     */
    if (!state || !state->info) {
        WARN_ON(1);
        return -EL3HLT;
    }

    port = state->port;
    circ = &state->info->xmit;

    if (!circ->buf)
        return 0;

    spin_lock_irqsave(&port->lock, flags);
    while (1) {
        c = CIRC_SPACE_TO_END(circ->head, circ->tail, UART_XMIT_SIZE);
        if (count < c)
            c = count;
        if (c <= 0)
            break;
        memcpy(circ->buf + circ->head, buf, c);
        circ->head = (circ->head + c) & (UART_XMIT_SIZE - 1);
        buf += c;
        count -= c;
        ret += c;
    }
    spin_unlock_irqrestore(&port->lock, flags);

    uart_start(tty);
    return ret;
}

Also, from the man write(3) documentation:

An attempt to write to a pipe or FIFO has several major characteristics:

  • Atomic/non-atomic: A write is atomic if the whole amount written in one operation is not interleaved with data from any other process. This is useful when there are multiple writers sending data to a single reader. Applications need to know how large a write request can be expected to be performed atomically. This maximum is called {PIPE_BUF}. This volume of IEEE Std 1003.1-2001 does not say whether write requests for more than {PIPE_BUF} bytes are atomic, but requires that writes of {PIPE_BUF} or fewer bytes shall be atomic.
Jason Orendorff
  • 42,793
  • 6
  • 62
  • 96
user239719
  • 51
  • 3

2 Answers2

3

I think that, technically, devices are not FIFOs, so it's not at all clear that the guarantees you quote are supposed to apply.

Are you concerned about partial writes and reads within a process, or are you actually reading and/or writing the same device from different processes? Assuming the latter, you might be better off implementing a proxy process of some sort. The proxy owns the device exclusively and performs all reads and writes, thus avoiding the multi-process atomicity problem entirely.

In short, I advise not attempting to verify that "reading/writing from this device is actually an atomic operation". It will be difficult to do with confidence, and leave you with an application that is subject to subtle failures if a later version of linux (or different o/s altogether) fails to implement atomicity the way you need.

Dale Hagglund
  • 16,074
  • 4
  • 30
  • 37
  • +1. Trying to infer API guarantees by reading the source code or by running stress tests against the current version sounds like a mistake. – Jason Orendorff Dec 28 '09 at 19:28
  • @Dale, Thanks for your reply. In answer to your question there is only a single process reading/writing, but I need to ensure that the read/write occurs without an interruption. That is to say, interrupts, preemption, everything which may interfere is halted for the duration of the write or read operation. I believe the transfer amount is trivially small, 16 bits or a couple factors of 2 close by. I am wary of implementing my own process to arbitrate transactions because I will be adding something which probably has bugs to something which may have bugs. Thank you again for your help. – user239719 Dec 28 '09 at 19:31
  • @Jason Thanks for your reply. What do you suggest in that case? – user239719 Dec 28 '09 at 19:42
  • Ok. But, I'm not sure what you mean by "without interruption"? Interrupts and pre-emption can't be disabled (at least without kernel changes). Do you have multiple threads in your process? If no, what's wrong with a standard loop around write that works through the data in whatever chunks are accepted by the driver. If yes, can you create a proxy thread, or alternatively use a mutex to control access to the device. Is there some kind of timing issue with the device your talking to? – Dale Hagglund Dec 28 '09 at 20:29
  • When I say "without interruption" I mean precisely that. So no interrupts, no context switches, no pause however brief, or of any kind, for the entire duration of each write or read. There are other threaded applications on the device, but I believe the one using RS485 will be single-threaded. I think the current implementation is as you described - a loop that iterates through chunks of data. There is no timing issue I am aware of - I have merely been tasked with verifying the atomicity of writing to this special device file since it is a non-standard usage scenario on non-standard hardware. – user239719 Dec 29 '09 at 17:09
  • It's very unlikely you can get the sort of extreme atomicity you're talking about (at least without kernel changes and/or a custom rtos). I think even attempting to verify this will be a waste of your time. I'd suggest trying to clarify the real concerns of whoever asked you to do this. Almost certainly, they don't really care about atomicity per se, but about some other higher level issue. The higher-level concern probably has a solution, but the atomicity issue does not, at least as you've defined it. – Dale Hagglund Jan 04 '10 at 13:56
2

I think PIPE_BUF is the right thing. Now, writes of less than PIPE_BUF bytes may not be atomic, but if they aren't it's an OS bug. I suppose you could ask here if an OS has known bugs. But really, if it has a bug like that, it just ought to be immediately fixed.

If you want to write more than PIPE_BUF atomically, I think you're out of luck. I don't think there is any way outside of application coordination and cooperation to make sure that writes of larger sizes happen atomically.

One solution to this problem is to put your own process in front of the device and make sure everybody who wants to write to the device contacts the process and sends the data to it instead. Then you can do whatever makes sense for your application in terms of atomicity guarantees.

Omnifarious
  • 54,333
  • 19
  • 131
  • 194
  • @Omnifarious, thanks for your reply. I checked the definition for PIPE_BUF, it looks like I am well below the threshold with my data structures. I agree with you that I am basically asking about known bugs. I also wonder more so because of the nature of the device I am using. It is easier for me to trust the completeness of the code for a desktop distribution which may be used by millions of people than the same on nonstandard hardware with a tiny customised kernel used by only a few people. Thank you again for your assistance, I appreciate it. – user239719 Dec 28 '09 at 20:00
  • Note that `PIPE_BUF` does vary significantly between Unix systems. See here for an overview of observed values: http://ar.to/notes/posix#pipe-buf – Arto Bendiken Oct 07 '14 at 23:45