Различия между C write call и Go syscall.Write - PullRequest
0 голосов
/ 29 августа 2018

системный вызов write возвращает -1, а установка errno - тривиальный случай. Меня интересует статус errno, если C write вызов с возвратом ноль или положительный. Оболочка syscall.Write в Go просто возвращает err, если errno не равно нулю для любого случая, что также включает в себя случай write, вызов возвращает положительное значение.

https://github.com/golang/go/blob/3cb64ea39e0d71fe2af554cbf4e99d14bc08d41b/src/syscall/zsyscall_linux_386.go#L1007

Однако справочная страница в C write call примерно описывает errno , может также быть установленной, но не указана, если мы напишем буфер нулевой длины без объяснения каких-либо подробностей.

Таким образом, следующие случаи кажутся неясными:

  1. Каков статус errno, если write вызов возвращает 0 для файла, неблокирующего сокета или блокирующего сокета?
  2. Когда и как write вызов с возвратом 0 и errno не равен 0?
  3. Каков статус errno, если write звонок возвращается положительный? Будет ли это отрицательно?
  4. Есть ли другие системные вызовы, которые могут столкнуться с такой же ситуацией?

Я думаю, что приведенное выше описание указывает на разницу между C write call и Go syscall.Write, которая не ясна для разработчиков, вот мои мысли:

Согласно man-странице, возвращение нуля четко определено в C write вызове для файлов и для неблокирующих сокетов, но неясно, существуют ли условия без ошибок для блокирующего сокета, которые привели бы к write() не блокирует, возвращает 0 и (предположительно), возможно, преуспеет позже при повторной попытке.

Действительно, Go идет непосредственно в системный вызов write. Однако следующий фрагмент кода кажется небезопасным, поскольку written, равный нулю, - это случай, который может вызвать err, но мы не хотим прерывать цикл:

func writeAll(fd int, buffer []byte) bool {
    length := len(buffer)
    for length > 0 {
        written, err := syscall.Write(fd, buffer)
        if err != nil { // here
            return false
        }
        length -= written
        buffer = buffer[written:]
    }
    return true
}

Что-то не так в моем подозрении?

1 Ответ

0 голосов
/ 29 августа 2018

При write нужно рассмотреть только два случая:

  1. Если произойдет сбой, результатом будет -1 и установлено errno.
  2. Если это удается, результат равен 0 или больше, и errno не установлено.

Нет других случаев для рассмотрения, если вы не заинтересованы в исторических реализациях Unix (см .: Является ли возвращаемое значение 0 из write (2) в C ошибкой? ).

Причина, по которой write может возвращать 0, заключается в том, что входной буфер может быть пустым.

Тем не менее, справочная страница вызова C write примерно описывает errno , может также быть установленным, но не указанным, если мы напишем буфер нулевой длины без объяснения каких-либо подробностей.

Все это означает, что возможна ошибка записи 0-длины. Если это не удалось, он возвращает -1 и устанавливает errno. Если это удается, он возвращает 0 и не устанавливает errno. Это то же самое поведение для любой другой записи, это только упомянуто на странице руководства, потому что люди могут найти удивительным, что запись 0 длины могла потерпеть неудачу.

Каково состояние errno, если write вызов возвращает 0 для файла, неблокирующего сокета или блокирующего сокета?

В этом случае errno не установлено, поскольку write не удалось. Это произойдет, только если входной буфер имеет нулевые байты.

Когда и как write вызов с возвратом 0 и errno не равен 0?

Этого не происходит. Либо errno установлено, и возвращаемое значение равно -1, либо errno не установлено, а возвращаемое значение равно 0 или больше.

Каков статус errno, если write звонок возвращается положительный? Будет ли это отрицательно?

Значение errno не будет установлено. Он будет иметь то же значение, что и до вызова write.

Есть ли другие системные вызовы, которые могут столкнуться с такой же ситуацией?

Как правило, системные вызовы либо возвращают ошибку , либо , они завершаются успешно. Они не будут делать смесь обоих. Посмотрите на раздел «Возвращаемое значение» на других справочных страницах, и вы увидите, что они в основном совпадают с write.

Код

Этот код безопасен.

func writeAll(fd int, buffer []byte) bool {
    length := len(buffer)
    for length > 0 {
        written, err := syscall.Write(fd, buffer)
        if err != nil { // here
            return false
        }
        length -= written
        buffer = buffer[written:]
    }
    return true
}

Обратите внимание, что это немного избыточно, мы можем просто сделать это:

func writeAll(fd int, buf []byte) bool {
    for len(buf) > 0 {
        n, err := syscall.Write(fd, buf)
        if err != nil {
            return false
        }
        buf = buf[n:]
    }
    return true
}

Записка о С

Технически, write - это и системный вызов, и функция C (по крайней мере, во многих системах). Однако функция C - это просто заглушка, которая вызывает системный вызов. Go не вызывает эту заглушку, она вызывает системный вызов напрямую, что означает, что C здесь не задействован (ну, пока вы не попадете в ядро).

Страница man показывает соглашения о вызовах и поведение заглушки C, write. Go решает скопировать это поведение в свою заглушку, syscall.Write. Реальный системный вызов имеет только интерфейс на языке ассемблера.

...