LINUX.ORG.RU

Где живут аллоцируемые куски памяти и файловые хэндлеры?

 , ,


2

3

Здравствуйте, системные програмисты

Подскажите, вот вызов malloc(size) возвращает указатель на выделенный кусок. При этом free не требует указывать размер.

Так-же open и socket возвращают лишь число

Возникает очевидное подозрение, что есть некие глобальные словари (массивы, списки), в которых ключ - это адрес (для malloc) или число (в случае open), а значение - структура, с настройками этих сущностей.

Почему сделанно именно так? Почему возвращается примитивное значение, а не, как это принято в Си, opaque структура, содержащая состояние?

Имеет ли право на жизнь подобный подход? Существует ли пример несистемных библиотек, которые возвращают число/указатель вместо структуры?

★★★★★

Последнее исправление: makoven (всего исправлений: 5)
Ответ на: комментарий от anonymous

если всю жизнь АТД делались на указателях.

Только если внутри одного процесса.

Как есть, бит в бит.

Не представляю, как это будет работать. Я могу придумать пару вариантов: передачу информации между пользовательским процессом и ядром, и передачу данных между процессами. Какой тогда смысл именно в указателе? Он просто превращается в идентификатор, ничем не отличающийся от файлового дескриптора.

Внутри одного процесса, конечно же, проще указатель использовать.

И не понял, что за массив структур, если это массив интов

Ты же о файловых дескрипторах говоришь? Значит нужно где-то хранить данные об открытом файле, информацию о том, как его системе найти, какие-то данные о буферах и так далее. Не хранить же каждый тип в отдельном массиве, каждый из которых может быть расширен. Слишком много мороки. В итоге всё равно всё сведётся к одному массиву структур-описателей. И вот там будет ещё одно поле, next.

и можно в каждом свободном слоте держать индекс следующего свободного.

В элементе нужно полей next столько же, сколько списков, в которых можно находиться одновременно. Я думал, это очевидно.

Странно, что все вы не сформулировали это сразу, а водили окольными путями.

Думаю, тут дело в том, что отвечающим это настолько очевидно, что они не понимают, как это может быть кому-то не понятно.

i-rinat ★★★★★
()
Ответ на: комментарий от deadskif

ЕМНИП, ядро работает в (расширенном) адресном пространстве процесса.

Про fork понятно, речь о том что и указатели в его случае будут работать не хуже. Именно инты имеют смысл при переходе через exec, при условии что это предопределенные числа. Фактически, речь про три стандартных потока.

anonymous
()
Ответ на: комментарий от anonymous

ЕМНИП, ядро работает в (расширенном) адресном пространстве процесса.

Там множество заморочек с MMU и всяким таким.

Про fork понятно, речь о том что и указатели в его случае будут работать не хуже.

Указатели на что?

Именно инты имеют смысл при переходе через exec, при условии что это предопределенные числа. Фактически, речь про три стандартных потока.

Любые. Главное что бы родительский и дочерний знали.

deadskif
()
Ответ на: комментарий от deadskif

Указатели на что?

На структуру файлового дескриптора. Мы сравниваем представление файлового дескриптора интом и указателем, так вот в этом контексте разницы нет.

Любые. Главное что бы родительский и дочерний знали.

Да, но (надеюсь) так в общем случае не делают. Может, это имело смысл когда не было развитого IPC.

anonymous
()
Ответ на: комментарий от i-rinat

Какой тогда смысл именно в указателе? Он просто превращается в идентификатор, ничем не отличающийся от файлового дескриптора.

Ну да. Смысл в том, что ядру тогда не пришлось бы заморачиваться на промежуточную таблицу.

В итоге всё равно всё сведётся к одному массиву структур-описателей.

Заранее распределенный массив структур? Это дофига памяти, к тому же в перспективе фрагментированной. Я говорю про массив указателей на структуры, лежащие в куче. Хотя не знаю, как там реально делают. Не суть, в любом случае идея со списком работает.

В элементе нужно полей next столько же, сколько списков, в которых можно находиться одновременно. Я думал, это очевидно.

Неочевидно, откуда появились другие списки. Наверно, ты говоришь про таблицу структур, а я про таблицу указателей.

Думаю, тут дело в том, что отвечающим это настолько очевидно, что они не понимают, как это может быть кому-то не понятно.

И поэтому дают любые ответы кроме правильного? хитрый_план.c

anonymous
()

Вообще можно выделить память без malloc.

int main()
{
	/* выбрал адрес */
	int addr = 0x0804a018;
	/* выделил память */
	int *a = (int *)addr;
	/* присвоил значение */
	*a = 10;
	/* вывел результат */
	printf("%p\n",a);
	/* освободил память */
	a = 0;
}
Ну это в таких случаях когда заранее известно сколько будет переменных.
А можно свою реализацию (malloc и free) написать, например как у меня.(пример malloc и free взят из linux/decompress/mm.h)
free_mem = значение, адрес где начинается свободная память.
free_mem_end = значение, адрес где заканчивается свободная память.
Дальше всё очень просто. Если ptr равен нулю, то ptr становится на адрес free_mem.
Потом надо узнать что ещё не вышли за границу свободных адресов.
И выделить память.
И так ptr будет дальше идти к free_mem_end и count всё расти от количества раз выделенной памяти.
Если память освобождать free, то освободив все выделенные адреса, можно будет снова получать адреса с free_mem (начало свободных адресов).

#include <stdio.h>
int free_mem = 0x0804a018;
int free_mem_end = 0x0804b018;
int ptr;
int count;

void *malloc(int size)
{
	void *p;

	if (size < 0)
		return NULL;

	if (!ptr)
		ptr = free_mem;

	ptr = (ptr + 3) & ~3;

	p = (void *)ptr;
	ptr += size;

	if (free_mem_end && ptr >= free_mem_end)
		return NULL;

	count++;
	return p;
}

void free(void *where)
{
	count--;
	if (!count)
		ptr = free_mem;
}

int main()
{
	/* выделил память */
	int *a = (int *)malloc(1);
	/* присвоил значение */
	*a = 10;
	/* вывел результат */
	printf("%p\n",a);
	/* освободил память */
	free(a);
}
u0atgKIRznY5
()
Ответ на: комментарий от makoven

Как минимум удобно было бы знать размер выделенной памяти по указателю. А тут как выяснилось такая функция есть, но она нестандартная, gnu-extension

а ещё malloc может быть переопределён на НЁХ со всеми последствиями (служебные данные будут совершенно другие).

MKuznetsov ★★★★★
()
Ответ на: комментарий от MKuznetsov

вот. еслиб была стандартная функция узнавания размера, то ее бы и в НЁХ определяли

makoven ★★★★★
() автор топика
chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |             Size of previous chunk, if allocated            | |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |             Size of chunk, in bytes                       |M|P|
       mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |             User data starts here...                          .
         .                                                               .
         .             (malloc_usable_size() bytes)                      .
         .                                                               |
 nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
         |             Size of chunk                                     |
         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Тебе возвращают указатель на mem.

anonymous
()
Ответ на: комментарий от anonymous

На структуру файлового дескриптора. Мы сравниваем представление файлового дескриптора интом и указателем, так вот в этом контексте разницы нет.

Структура в ядре. Ядерный адрес ядро все-равно будет проверять. Непосредственно пользовать ядерный адрес приложение не может. Никакой разницы, кроме того, что проще с числами с 0.

Да, но (надеюсь) так в общем случае не делают.

А что в этом такого?

deadskif
()
Ответ на: комментарий от anonymous

ядру тогда не пришлось бы заморачиваться на промежуточную таблицу

Тогда надо придётся нехило так поднапрячься, чтобы один процесс не мог похакать любой другой.

i-rinat ★★★★★
()
Ответ на: комментарий от utf8nowhere

екоторые считают, что кроме указателя должен возвращаться размер выделенной памяти. Это на порядок упростило бы жизнь разработчикам менеджеров памяти и повысило производительность этих менеджеров. Особенно в C++.

А разве в C++ нельзя назначить свой аллокатор для new/delete?

Deleted
()
Ответ на: комментарий от i-rinat

Эээ... А что, замена дескриптора на указатель решит хоть одну из упомянутых проблем?

Там же речь об обратном, замена указателя на какой-нибудь другой идентификатор не избавляет от проблем управления ресурсами, лучше (правильнее, безопаснее) не становится. fd в этом контексте самый неудачный пример полезности идентификаторов.

mashina ★★★★★
()
Ответ на: комментарий от makoven

а можно поинтересоваться что еще надо сделать с сокетом кроме close()

close() закрывает файловый дескриптор, но в общем случае не уничтожает объект который находится за ним. Так, если для объекта создано более одного fd (dup()'ом или автоматом создалисть копии после fork() или exeve(), то close() на сокете не закроет соединение и оно останется висеть пока не сдохнут все ссылки на него в виде fd. Правильно делать shutdown() + close(), но не только из-за описанной проблемы.

...и какие пляски надо совершить для fd во время fork()?

Соответственно, нужно позакрывать все ненужные унаследованные fd чтобы не было описанных выше проблем и порождённый процесс не мог как-то нештатно ими воспользоваться.

mashina ★★★★★
()
Ответ на: комментарий от mashina

да, действительно пляски. Какой вообще смысл дублировать дескрипторы в функции execve, если эта функция вызывает совсем другую программу, которой мои дескрипторы нафиг не сдались..

system() я так понимаю тоже дублирует дескрипторы? То-есть, когда я в программе на lua, в которой открыто несколько сокетов, вызываю system.execute, в вызванный bash процесс клонируются все мои открытые файлы и сокеты? Безумие какое-то

makoven ★★★★★
() автор топика
Последнее исправление: makoven (всего исправлений: 1)
Ответ на: комментарий от mashina

fd в этом контексте самый неудачный пример полезности идентификаторов.

Представим реализацию ядра, у которого в API при открытии файла в user-mode возвращается указатель. А для read/write нужно передавать этот же указатель обратно. Допустим, он указывает на какую-то структуру внутри. Возникают вопросы:

  1. пользовательский процесс может разыменовывать указатель?
  2. Если да, что там?
  3. Можно ли туда писать?
  4. Если да, как ядро будет следить за корректностью данных, которые может подправить процесс?
  5. Там все данные, относящиеся к открытому файлу или есть какая-то приватная информация только для ядра?
  6. Если есть приватная информация, как ядро её находит?
  7. Как приватная информация скрывается от пользовательского процесса?
  8. Что будет, если пользователь передаст некорректный указатель? Что если он станет не выровненным?
  9. Если указатель нельзя разыменовывать, в чём его смысл как указателя для пользователя?
i-rinat ★★★★★
()
Ответ на: комментарий от anonymous

в stl явно указывается колво деаллоцируемой памяти в аллокаторах.

i36_zubov
()
Ответ на: комментарий от deadskif

Никакой разницы, кроме того, что проще с числами с 0.

Че-то контекст теряется. Там выше высказывалась мысль что инты лучше, потому что ими можно пользоваться в другом адресном пространстве и это часто используется при fork. Я возразил что fork его все равно не меняет, и указатели тоже работали бы. Но я согласился что при exec интовые fd сохраняют смысл, хотя лично мне этот механизм кажется костылем.

А что в этом такого?

Магические числа, пляски с dup2, много неочевидного. Локальный сокет и другие варианты - проще и надежнее.

anonymous
()
Ответ на: комментарий от i-rinat

Могу ответить из своего представления.

Если указатель нельзя разыменовывать, в чём его смысл как указателя для пользователя?

Ни в чем, также как int fd не имеет смысла как инт. Разница есть для ядра, которое избавляется от лишней косвенности.

Валидация дескриптора - вот где инт получает смысл. Но при желании и указатель можно валидировать, например писать в дескриптор сигнатуру.

anonymous
()
Ответ на: комментарий от anonymous

Но при желании и указатель можно валидировать, например писать в дескриптор сигнатуру.

Отдаём недопустимый адрес и ядро ловит исключение. Sweet.

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Во-первых, ядро знает какие адреса допустимы и при желании может проверить. Во-вторых, ядро никогда не защищало от сегфолтов.

anonymous
()
Ответ на: комментарий от anonymous

и при желании может проверить

От него это ожидается. И ещё ожидается, что одному процессу не дадут заглянуть в файлы другого процесса. То есть всё равно придётся валидировать адрес по некоторой таблице, отдельной для каждого процесса. Что приводит нас к...

i-rinat ★★★★★
()
Ответ на: комментарий от i-rinat

Согласен что так проще всего.

anonymous
()
Ответ на: комментарий от anonymous

Я возразил что fork его все равно не меняет, и указатели тоже работали бы

Указатели в ядро — это точно такие же просто числа, как и fd. Но вот объяснить, почему их нельзя разыменовывать, пришлось бы каждый раз всем. Да и зачем в случае sizeof(int) < sizeof(void *) использовать больший тип непонятно.

Магические числа, пляски с dup2, много неочевидного. Локальный сокет и другие варианты - проще и надежнее.

for(;;) {
    int sock = accept(lsock, (struct sockaddr *) &addr, &addrlen);
    /* bla-bla */
    if ((pid = fork()) == 0) {
       int r = read(sock, buf, sizeof(buf));
       write(sock, buf, r);
       exit(0);
    }
    /* bla-bla */
}

Как это сделать проще и надежнее через локальный сокет?

deadskif
()
Ответ на: комментарий от makoven

system() я так понимаю тоже дублирует дескрипторы? То-есть, когда я в программе на lua ... в вызванный bash процесс клонируются все мои открытые файлы и сокеты?

Конкретно для exec~() у файловых дескрипторов есть специальный флаг close-on-exec запрещающий наследование. Но его нужно ставить руками через fcntl() или указывать сразу (см. например, open() и O_CLOEXEC, почти у всех подобных ф-ий в топике есть аналог).

Как работает system() в lua я не знаю, но вероятно ничего специального не делает и тоже ведёт к наследованию всех fd без close-on-exec флага.

mashina ★★★★★
()
Ответ на: комментарий от i-rinat

Представим реализацию ядра, у которого в API при открытии файла в user-mode...

Ты сейчас уходишь в другую тему. В ядре файловые дескрипторы используются не для того, чтобы сделать «API крепче» и не ради защиты от каких-либо ошибок.

mashina ★★★★★
()
Ответ на: комментарий от i-rinat

В случае с fd нет выбора между программным объектом и идентификатором, использование последнего продиктовано в первую очередь отсутствием альтернативных вариантов.

mashina ★★★★★
()
Ответ на: комментарий от deadskif

Но вот объяснить, почему их нельзя разыменовывать, пришлось бы каждый раз всем

FILE* - всем все понятно. А с неполным объявлением тут и не ошибешься. Да и играть с интами вроде никто не пытается.

Как это сделать проще и надежнее через локальный сокет?

Контекст окончательно потерялся :( Речь шла про наследование дескрипторов при exec как механизм IPC.

anonymous
()
Ответ на: комментарий от anonymous

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

Речь шла про наследование дескрипторов при exec как механизм IPC.

Берем пример из man-а.


           cpid = fork();
           if (cpid == -1) {
               perror("fork");
               exit(EXIT_FAILURE);
           }

           if (cpid == 0) {    /* Потомок читает из канала */
               close(pipefd[1]);          /* Закрывает неиспользуемый конец для записи */

               while (read(pipefd[0], &buf, 1) > 0)
                   write(STDOUT_FILENO, &buf, 1);

               write(STDOUT_FILENO, "\n", 1);
               close(pipefd[0]);
               _exit(EXIT_SUCCESS);

           } else {            /* Родитель пишет значение argv[1] в канал */
               close(pipefd[0]);          /* Закрывает неиспользуемый конец для чтения */
               write(pipefd[1], argv[1], strlen(argv[1]));
               close(pipefd[1]);          /* Читатель видит EOF */
               wait(NULL);                /* Ожидание потомка */
               exit(EXIT_SUCCESS);
           }
       }
И заменяем код ребенка на execl(/* bla-bla-bla */), аргументом передаем значение pipefd[0].

deadskif
()
Ответ на: комментарий от deadskif

FILE * можно разыменовывать, в общем то, если очень хочется

... понарушать стандарт. Да, где-то можно, но вообще-то в API можно это исключить. Но мне все равно непонятно желание это делать. Вы с интовыми дескрипторами что делаете - инкременты, битовые сдвиги, ...?

Берем пример из man-а

Да я в курсе как это делается. Вот эти close, эта передача дескриптора через argv - чем все это лучше простого mkfifo? Подавляющее большинство процессов это не использует, зачем для них клонируются все дескрипторы? Это и трата ресурсов, и потенциальные баги.

anonymous
()
Ответ на: комментарий от anonymous

Но мне все равно непонятно желание это делать. Вы с интовыми дескрипторами что делаете - инкременты, битовые сдвиги, ...?

А мне не понятно ваше желание указателей вникуда.

Вот эти close, эта передача дескриптора через argv - чем все это лучше простого mkfifo?

fifo потом еще удалять надо. И возиться с mkstemp возможно. Еще подобное я несколько раз видел в шеловских скриптах для логгирования, например.

deadskif
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.