Вопрос о макросе round_up
#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))
С помощью вышеуказанного макроса кто-то может помочь мне в понимании части "(s) -1", почему?
а также макросы вроде:
#define PAGE_ROUND_DOWN(x) (((ULONG_PTR)(x)) & (~(PAGE_SIZE-1)))
#define PAGE_ROUND_UP(x) ( (((ULONG_PTR)(x)) + PAGE_SIZE-1) & (~(PAGE_SIZE-1)) )
Я знаю, что "(~ (PAGE_SIZE-1)))" часть будет обнулять последние пять бит, но кроме этого я не знаю, особенно роль "&" оператор играет.
Спасибо,
Ответы
Ответ 1
Макрос ROUND_UP
полагается на целочисленное деление, чтобы выполнить задание. Он будет работать, только если оба параметра являются целыми числами. Я предполагаю, что N
- это число, подлежащее округлению, а S
- это интервал, на котором он должен быть округлен. То есть ROUND_UP(12, 5)
должен возвращать 15, так как 15 - это первый интервал 5, превышающий 12.
Представьте, что мы округлили, а не вверх. В этом случае макрос будет просто:
#define ROUND_DOWN(N,S) ((N / S) * S)
ROUND_DOWN(12,5)
вернет 10, потому что (12/5)
в целых делениях равно 2, а 2 * 5 равно 10. Но мы не делаем ROUND_DOWN, мы делаем ROUND_UP. Поэтому, прежде чем делать целочисленное деление, мы хотим добавить столько, сколько можем, не теряя точности. Если бы мы добавили S
, это будет работать практически в каждом случае; ROUND_UP(11,5)
станет (((11 + 5)/5) * 5), а так как 16/5 в целых делениях равно 3, мы получим 15.
Проблема возникает, когда мы передаем число, которое уже округлено до указанного множителя. ROUND_UP(10, 5)
вернет 15, и это неправильно. Поэтому вместо добавления S мы добавляем S-1. Это гарантирует, что мы никогда не будем толкать что-то до следующего "ведра" без необходимости.
Макросы PAGE_
связаны с бинарной математикой. Мы сделаем вид, что имеем дело с 8-битными значениями для простоты. Предположим, что PAGE_SIZE
есть 0b00100000
. Таким образом, PAGE_SIZE-1
0b00011111
. ~(PAGE_SIZE-1)
тогда 0b11100000
.
Двоичный &
будет выровнять два двоичных числа и оставить 1 в любом месте, где оба числа имеют 1. Таким образом, если x
был 0b01100111, операция будет выглядеть следующим образом:
0b01100111 (x)
& 0b11100000 (~(PAGE_SIZE-1))
------------
0b01100000
Вы заметите, что операция действительно только обнуляла последние 5 бит. Все это. Но это была именно та операция, которая необходима для округления до ближайшего интервала PAGE_SIZE
. Обратите внимание, что это работало только потому, что PAGE_SIZE
было ровно степенью 2. Это немного напоминает высказывание, что для любого произвольного десятичного числа вы можете округлить до ближайших 100 просто путем обнуления двух последних цифр. Он работает отлично и действительно легко сделать, но не будет работать вообще, если вы пытаетесь округлить до ближайшего кратного 76.
PAGE_ROUND_UP
делает то же самое, но добавляет столько, сколько может, на страницу, прежде чем отрезать ее. Мне нравится, как я могу округлить до ближайшего кратного 100, добавив 99 к любому числу, а затем обнулить последние две цифры. (Добавим PAGE_SIZE-1
по той же причине, что и мы добавили S-1
выше.)
Удачи в вашей виртуальной памяти!
Ответ 2
Использование целочисленной арифметики, деление всегда округляется вниз. Чтобы исправить это, вы добавите максимально возможное число, которое не повлияет на результат, если исходный номер был равномерно делимым. Для числа S наибольшее возможное число - S-1.
Округление до степени 2 является особенным, потому что вы можете сделать это с помощью битовых операций. Множество из двух будет иметь ноль в нижнем бите, кратное 4 всегда будет иметь нуль в нижних двух битах и т.д. Двоичное представление мощности 2 - это один бит, за которым следует куча нулей; вычитание 1 очистит этот бит и установит все биты вправо. Инвертирование этого значения создает битную маску с нулями в местах, которые необходимо очистить. Оператор и очистит эти биты в вашем значении, таким образом округляя значение вниз. Тот же трюк добавления (PAGE_SIZE-1) к исходному значению заставляет его округлять, а не вниз.
Ответ 3
Макросы округления страницы предполагают, что `PAGE_SIZE является степенью двух, например:
0x0400 -- 1 KiB
0x0800 -- 2 KiB`
0x1000 -- 4 KiB
Значением PAGE_SIZE - 1
, следовательно, является все бит:
0x03FF
0x07FF
0x0FFF
Следовательно, если целые числа составляли 16 бит (вместо 32 или 64 - это меня сбивало с некоторым типом), то значение ~(PAGE_SIZE-1)
равно:
0xFC00
0xFE00
0xF000
Когда вы берете значение x
(предполагая, неправдоподобно для реальной жизни, но достаточное для целей изложения, что ULONG_PTR
- это неподписанное 16-битовое целое число) 0xBFAB
, то
PAGE_SIZE PAGE_ROUND_DN(0xBFAB) PAGE_ROUND_UP(0xBFAB)
0x0400 --> 0xBC00 0xC000
0x0800 --> 0xB800 0xC000
0x1000 --> 0xB000 0xC000
Макросы округляются вниз и до ближайшего кратного размера страницы. Последние пять бит будут только обнулены, если PAGE_SIZE == 0x20
(или 32).
Ответ 4
В соответствии с текущим проектом стандарта (C99) этот макрос не совсем корректен, но обратите внимание, что для отрицательных значений N
результат будет почти наверняка неверным.
Формула:
#define ROUND_UP(N, S) ((((N) + (S) - 1) / (S)) * (S))
Использует тот факт, что целочисленное деление округляет вниз для неотрицательных целых чисел и использует часть S - 1
, чтобы заставить его округлить.
Однако целочисленное деление округляется к нулю (C99, раздел 6.5.5. Мультипликативные операторы, п. 6). Для отрицательного N
правильный способ "округления": "N / S
", не более, не меньше.
Он становится еще более привлекательным, если S
также может быть отрицательным значением, но пусть даже не туда... (см. Как я могу обеспечить, чтобы деление из целых чисел всегда округляется? для более подробного обсуждения различных неправильных и одного или двух правильных решений)
Ответ 5
The и делает это так. Хорошо, давайте возьмем несколько двоичных чисел.
(with 1000 being page size)
PAGE_ROUND_UP(01101b)=
01101b+1000b-1b & ~(1000b-1b) =
01101b+111b & ~(111b) =
01101b+111b & ...11000b = (the ... means 1 continuing for size of ULONG)
10100b & 11000b=
10000b
Итак, как вы можете видеть (надеюсь). Это округляет, добавляя PAGE_SIZE к x, а затем ANDing, чтобы он отменил нижние бит PAGE_SIZE, которые не установлены