論加鎖否:程式日誌

安全劍客發表於2020-01-21
生活中的日誌是記錄你生活的點點滴滴,讓它把你內心的世界表露出來,更好的詮釋自己的內心世界,而電腦裡的日誌可以是有價值的資訊寶庫,也可以是毫無價值的資料泥潭。
日誌(log)

為了讓自己的思路更加清晰,下面我都會稱日誌為 log。因為日誌這個詞有兩種含義。

日記的另一種說法。“志”字本身為“記錄”的意思,日誌就為每日的記錄(通常是跟作者有關的)。

伺服器日誌(server log),記錄伺服器等電腦裝置或軟體的運作。

我們這裡說的當然是伺服器日誌,也就是 server log 。

寫入 log

一般寫入 log 都會遵循以下步驟:

int fd = open(path)
write(fd, sign_append)
fclose(fd)

解釋一下上面的程式碼:

int fd = open(path)

會透過系統呼叫開啟一個檔案描述符,或者在其他語言中也可以稱作資源描述符,資源型別,或控制程式碼。

write(fd, append = 1)

write 系統呼叫,並加上 append 標誌,會執行 seek 和 write 兩個系統呼叫,但是這種系統呼叫是原子性的。

原子性意味著 seek 和 write 會同時執行,不會有兩個執行緒產生交叉,必須 a 執行緒執行完 seek 和 write ,b 執行緒才能繼續執行(這裡說執行緒,是因為執行緒才是 cpu 排程的基本單位)。

所以在 nginx 中,我們加上 append 標誌,就不用對執行緒上鎖了。

fclose(fd)

關閉描述符。

 一般對開啟的檔案描述符有一個最大數量的限制,如果不關閉描述符,很有可能造成大 bug。

檢視linux 中限制的方法
$ ulimit -a
core file size (blocks, -c) 0
data seg size (kbytes, -d) unlimited
scheduling priority (-e) 0
file size (blocks, -f) unlimited
pending signals (-i) 15732
max locked memory (kbytes, -l) 64
max memory size (kbytes, -m) unlimited
open files (-n) 1024
pipe size (512 bytes, -p) 8
POSIX message queues (bytes, -q) 819200
real-time priority (-r) 0
stack size (kbytes, -s) 8192
cpu time (seconds, -t) unlimited
max user processes (-u) 15732
virtual memory (kbytes, -v) unlimited
file locks (-x) unlimited

論加鎖否:程式日誌論加鎖否:程式日誌

所以,如果是系統呼叫,那麼 append 不用加鎖。

為什麼 php 語言寫日誌時用了 append 也要加鎖?

如果根據上面的說法,我們們可以設定好 write 的 append 標誌,然後就可以睡大覺去了,檔案永遠不會衝突。

但是(一般都有個但是)你去看 php 的框架中都會在 file_put_contents 的 append 之前加鎖。

於是,懷疑是因為 file_put_contents 的底層實現沒有實現原子性。

跟進原始碼(非 php 程式設計師或者對 php 底層原始碼無興趣的可以跳過了):

file_put_contents底層實現
 // file.c
/* {{{ proto int|false file_put_contents(string file, mixed data [, int flags [, resource context]])
   Write/Create a file with contents data and return the number of bytes written */
PHP_FUNCTION(file_put_contents)
{
...
case IS_STRING:
   if (Z_STRLEN_P(data)) {
      numbytes = php_stream_write(stream, Z_STRVAL_P(data), Z_STRLEN_P(data));
      if (numbytes != Z_STRLEN_P(data)) {
         php_error_docref(NULL, E_WARNING, "Only %zd of %zd bytes written, possibly out of free disk space", numbytes, Z_STRLEN_P(data));
         numbytes = -1;
      }
   }
   break;
...
}
// php_streams.h
PHPAPI ssize_t _php_stream_write(php_stream *stream, const char *buf, size_t count);
#define php_stream_write_string(stream, str)   _php_stream_write(stream, str, strlen(str))
#define php_stream_write(stream, buf, count)   _php_stream_write(stream, (buf), (count))
// streams.c
PHPAPI ssize_t _php_stream_write(php_stream *stream, const char *buf, size_t count)
{
  ...
   if (stream->writefilters.head) {
      bytes = _php_stream_write_filtered(stream, buf, count, PSFS_FLAG_NORMAL);
   } else {
      bytes = _php_stream_write_buffer(stream, buf, count);
   }
   if (bytes) {
      stream->flags |= PHP_STREAM_FLAG_WAS_WRITTEN;
   }
   return bytes;
}
/* Writes a buffer directly to a stream, using multiple of the chunk size */
static ssize_t _php_stream_write_buffer(php_stream *stream, const char *buf, size_t count){
...
while (count > 0) {
   ssize_t justwrote = stream->ops->write(stream, buf, count);
   if (justwrote <= 0) { /* If we already successfully wrote some bytes and a write error occurred * later, report the successfully written bytes. */ if (didwrite == 0) { return justwrote; } return didwrite; } buf += justwrote; count -= justwrote; didwrite += justwrote; /* Only screw with the buffer if we can seek, otherwise we lose data * buffered from fifos and sockets */ if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) {
      stream->position += justwrote;
   }
}
}
// php_streams.h
/* operations on streams that are file-handles */
typedef struct _php_stream_ops  {
   /* stdio like functions - these are mandatory! */
   ssize_t (*write)(php_stream *stream, const char *buf, size_t count);
   ssize_t (*read)(php_stream *stream, char *buf, size_t count);
   int    (*close)(php_stream *stream, int close_handle);
   int    (*flush)(php_stream *stream);
   const char *label; /* label for this ops structure */
   /* these are optional */
   int (*seek)(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffset);
   int (*cast)(php_stream *stream, int castas, void **ret);
   int (*stat)(php_stream *stream, php_stream_statbuf *ssb);
   int (*set_option)(php_stream *stream, int option, int value, void *ptrparam);
} php_stream_ops;
 
// plain_wrapper.c
static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t count)
{
   php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract;
   assert(data != NULL);
   if (data->fd >= 0) {
#ifdef PHP_WIN32
      ssize_t bytes_written;
      if (ZEND_SIZE_T_UINT_OVFL(count)) {
         count = UINT_MAX;
      }
      bytes_written = _write(data->fd, buf, (unsigned int)count);
#else
      ssize_t bytes_written = write(data->fd, buf, count);
#endif
      if (bytes_written < 0) { if (errno == EWOULDBLOCK || errno == EAGAIN) { return 0; } if (errno == EINTR) { /* TODO: Should this be treated as a proper error or not? */ return bytes_written; } php_error_docref(NULL, E_NOTICE, "write of %zu bytes failed with errno=%d %s", count, errno, strerror(errno)); } return bytes_written; } else { #if HAVE_FLUSHIO if (data->is_seekable && data->last_op == 'r') {
         zend_fseek(data->file, 0, SEEK_CUR);
      }
      data->last_op = 'w';
#endif
      return (ssize_t) fwrite(buf, 1, count, data->file);
   }
}

這個函式最終呼叫的是函式 php_stdiop_write

函式 _php_stream_write_buffer 中會將字串分成多個 chunksize ,每個 chunksize 為 8192 (8K) 位元組,分別進行 write。

如果不加鎖,那麼超過 8192 位元組之後,多個程式寫日誌就會出現混亂。

而且,php 文件也說明了:

論加鎖否:程式日誌論加鎖否:程式日誌

論加鎖否:程式日誌論加鎖否:程式日誌

原文地址:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31559985/viewspace-2674172/,如需轉載,請註明出處,否則將追究法律責任。

相關文章