четверг, 2 октября 2008 г.

dd или cat?

Привычка использовать команду dd всегда, когда чтение/запись идет с блоковым устройством у линуксоида, как говорится, в крови. Но ведь команда cat в linux тоже позволяет читать/писать двоичные данные? Заглянув в код команды cat, мы увидим, что сегодня она уже не такая простая какой была когда-то в первых UNIX.
Простейший путь:
      insize = ST_BLKSIZE (stat_buf);
/* ... */
/* Select which version of `cat' to use. If any format-oriented
options were given use `cat'; otherwise use `simple_cat'. */
/* ... */
insize = max (insize, outsize);
inbuf = xmalloc (insize + page_size - 1);

ok &= simple_cat (ptr_align (inbuf, page_size), insize);

Макрос ST_BLKSIZE довольно наворочен, но в Linux, его значение действительно будет соответствовать размеру блока устройства. Кстати, а вот как выглядела команда cat в 1972 году. Еще интересная штука, это выравнивание буффера на границу страницы. :) В этом письме говорится зачем это сделано. "I can't measure any performance improvements on my host, but I suspect that aligning I/O buffers can make a real
difference with some device drivers on some hosts, and it shouldn't hurt on other hosts."
-- вот так то. :)


Код команда dd почти в 3 раза больше cat. И, в общем, понятно почему. В dd чтение и запись всегда блочные и полностью конфигурируемые. По умолчанию размер блока 512 (что почти всегда плохо). Вывод команды dd также выполняет блочную запись, а не просто вывод данных на stdout (хотя по умолчанию это так).
Посмотрим на рутины чтения данных в cat:
size_t
safe_rw (int fd, void const *buf, size_t count)
{
/* .... */
for (;;)
{
ssize_t result = rw (fd, buf, count);

if (0 <= result)
return result;
else if (IS_EINTR (errno))
continue;
/* ... */
}
}

/* ..simple_cat.. */
for (;;)
{
/* Read a block of input. */

n_read = safe_read (input_desc, buf, bufsize);
/* ... */
if (n_read == 0)
return true;
/* Write this block out. */
{
/* The following is ok, since we know that 0 < n_read. */
size_t n = n_read;
if (full_write (STDOUT_FILENO, buf, n) != n)
error (EXIT_FAILURE, errno, _("write error"));
}
}

и в dd:
static ssize_t
iread (int fd, char *buf, size_t size)
{
for (;;)
{
ssize_t nread;
process_signals ();
nread = read (fd, buf, size);
if (! (nread < 0 && errno == EINTR))
return nread;
}
}
/* ... dd_copy ... */
nread = iread (STDIN_FILENO, ibuf, input_blocksize);

if (nread == 0)
break; /* EOF. */

if (nread < 0)
{
error (0, errno, _("reading %s"), quote (input_file));
if (conversions_mask & C_NOERROR)
{
print_stats ();
/* Seek past the bad block if possible. */
if (!advance_input_after_read_error (input_blocksize - partread))
{
exit_status = EXIT_FAILURE;

/* Suppress duplicate diagnostics. */
input_seekable = false;
input_seek_errno = ESPIPE;
}
if ((conversions_mask & C_SYNC) && !partread)
/* Replace the missing input with null bytes and
proceed normally. */
nread = 0;
else
continue;
}
else
{
/* Write any partial block. */
exit_status = EXIT_FAILURE;
break;
}
/* ... */
if (n_bytes_read < input_blocksize)
{
r_partial++;
partread = n_bytes_read;
if (conversions_mask & C_SYNC)
{
if (!(conversions_mask & C_NOERROR))
/* If C_NOERROR, we zeroed the block before reading. */
memset (ibuf + n_bytes_read,
(conversions_mask & (C_BLOCK | C_UNBLOCK)) ? ' ' : '\0',
input_blocksize - n_bytes_read);
n_bytes_read = input_blocksize;
}
}
else
{
r_full++;
partread = 0;
}

И мы видим, что вроде бы все одинаково, но цикл чтения команды cat, это последовательное чтение. А цикл чтения dd это ВСЕГДА поблочное чтение, и если по каким-то причинам, произошло чтение меньшего количества байт, чем в блоке, dd имеет возможность пропустить "плохие" входные данные.

Здесь один из участников темы высказал следующую мысль. Разделение чтения/записи на блочное и не блочное -- сложилось исторически. На традиционных Unix блоковые диски были доступны и как блоковые и как символьные устройства. dd была нужна для работы с блочным устройством, а cat умела работать только с символьными. Поэтому сегодня, различие между этими командами скорее функциональное, чем архитектурное.

Комментариев нет:

Архив блога