Почему мой модуль ядра отлично выполняет float-разделение? [Дубликат]

tl; dr: Нет! Функции стрелок и декларации функций / выражения не являются эквивалентными и не могут быть заменены вслепую. Если функция, которую вы хотите заменить, not использует this, arguments и не вызывается с new, тогда да.


Как это часто бывает: это зависит. Функции Arrow имеют другое поведение, чем декларации / выражения функций, поэтому давайте сначала рассмотрим различия:

1. Функции Lexical this и arguments

не имеют собственных привязок this или arguments. Вместо этого эти идентификаторы разрешаются в лексической области, как и любая другая переменная. Это означает, что внутри функции стрелки this и arguments относятся к значениям this и arguments в окружающей среде, функция стрелки определена в (т.е. «снаружи» стрелка )

В случае выражения функции, this относится к объекту, который был создан внутри createObject. В функциональном случае стрелки this относится к this самого createObject.

Это делает функции стрелок полезными, если вам нужно получить доступ к this текущей среды:

// currently common pattern
var that = this;
getData(function(data) {
  that.data = data;
});

// better alternative with arrow functions
getData(data => {
  this.data = data;
});

Обратите внимание, что это также означает, что не можно установить функцию стрелки this с .bind или .call.

Если вы не очень знакомы с this, рассмотрим чтение

2. Функции стрелок не могут быть вызваны с помощью new

ES2015 различает функции, доступные call , и функции, которые являются конструкцией . Если функция конструируется, ее можно вызвать с помощью new, то есть new User(). Если функция является вызываемой, ее можно вызвать без new (т. Е. Вызов нормальной функции).

Функции, созданные посредством деклараций / выражений функций, являются конструктивными и вызываемыми. Функции стрелок (и методы) являются только вызываемыми. class конструкторы только конструктивны.

Если вы пытаетесь вызвать функцию, не вызываемую вызовом, или построить неконструируемую функцию, вы получите ошибку времени выполнения.


Зная это, мы можем указать следующее.

Сменный:

  • Функции, которые не используют this или arguments.
  • Функции, которые используются с .bind(this)

Не сменный:

  • Функции конструктора
  • Функция / методы, добавленные к прототипу (поскольку они обычно используют функции this)
  • Variadic (если они используют arguments (см. ниже))

Давайте рассмотрим это более подробно с помощью ваших примеров:

Функция конструктора

Это не будет работать, потому что функции стрелок нельзя вызвать с помощью new. Продолжайте использовать объявление / выражение функции или используйте class.

Способы прототипа

Скорее всего нет, потому что методы прототипа обычно используют this для доступа к экземпляру. Если они не используют this, вы можете его заменить. Однако, если вы в первую очередь заботитесь о сжатом синтаксисе, используйте class с его синтаксисом сжатого метода:

class User {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

Методы объекта

Аналогично для методов в объектном литерале. Если метод хочет ссылаться на сам объект через this, продолжайте использовать функциональные выражения или используйте новый синтаксис метода:

const obj = {
  getName() {
    // ...
  },
};

Обратные вызовы

Это зависит. Вы должны обязательно заменить его, если вы наложили внешний this или используете .bind(this):

// old
setTimeout(function() {
  // ...
}.bind(this), 500);

// new
setTimeout(() => {
  // ...
}, 500);

Но: Если код, вызывающий обратный вызов, явно устанавливает this на определенное значение , как это часто бывает с обработчиками событий, особенно с jQuery, и обратный вызов использует this (или arguments), вы не можете использовать функцию стрелки!

Variadic функции

Поскольку функции стрелок не имеют собственных arguments, вы не можете просто заменить их функцией стрелки. Однако ES2015 вводит альтернативу использованию arguments: параметр rest .

// old
function sum() {
  let args = [].slice.call(arguments);
  // ...
}

// new
const sum = (...args) => {
  // ...
};

Связанный вопрос:

Дополнительные ресурсы:

9
задан Vilhelm Gray 8 April 2013 в 18:51
поделиться

4 ответа

Я думал, что вы не можете выполнять операции с плавающей запятой в ядре Linux

Вы не можете безопасно: отказ в использовании kernel_fpu_begin() / kernel_fpu_end() не работает средние команды FPU будут ошибочными (не на x86 по крайней мере).

Вместо этого он будет автоматически искажать состояние FPU пользовательского пространства. Это плохо; не выполняйте этого.

Компилятор не знает, что означает kernel_fpu_begin(), поэтому он не может проверить / предупредить код, который компилируется в инструкции FPU за пределами областей начала FPU.

Может существовать режим отладки, когда ядро ​​отключает инструкции SSE, x87 и MMX вне областей kernel_fpu_begin / end, но это будет медленнее и не будет выполнено по умолчанию.

Возможно, однако: установка CR0::TS = 1 приводит к сбою инструкций x87, поэтому возможен ленивый переключение контекста FPU, а для SSE и AVX есть другие биты.


Есть Многие способы для кода ошибки ядра приводят к серьезным проблемам. Это всего лишь один из многих. В C вы почти всегда знаете, когда используете плавающую точку (если только опечатка не приводит к константе 1. или что-то в контексте, который действительно компилируется).


Почему FP архитектурное состояние, отличное от целого?

Linux должен сохранять / восстанавливать целочисленное состояние в любое время, когда он входит / выходит из ядра. Весь код должен использовать целые регистры (за исключением гигантского прямоугольного блока вычисления FPU, который заканчивается на jmp вместо ret (ret изменяет rsp).)

Но код ядра вообще избегает FPU, поэтому Linux оставляет состояние FPU несохраненным при записи из системного вызова, сохраняя только до фактического переключения контекста на другой процесс user-space или на kernel_fpu_begin. В противном случае, как правило, возвращаются к одному и тому же процессу пространства пользователя на одном ядре, поэтому состояние FPU не нужно восстанавливать, потому что ядро ​​его не трогало. (И вот где коррупция произойдёт, если задача ядра действительно изменит состояние FPU. Я думаю, это происходит в обоих направлениях: пользовательское пространство может также испортить ваше состояние FPU ).

Целочисленное состояние довольно мало, только 16x 64-разрядные регистры + RFLAGS и сегментные регистры. Состояние FPU более чем в два раза больше даже без AVX: 8x 80-бит x87 регистров и 16x XMM или YMM или 32x ZMM-регистров (+ MXCSR и x87 status + контрольные слова). Также регистры MPX bnd0-4 объединены с «FPU». На данный момент «состояние FPU» означает все нецелые регистры. На моем Skylake dmesg говорит x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.

См. Понимание использования FPU в ядре linux ; современная Linux не делает ленивых контекстных переключателей FPU по умолчанию для контекстных переключателей (только для переходов ядра / пользователя). (Но в этой статье объясняется, что такое Lazy.)

Большинство процессов используют SSE для копирования / обнуления небольших блоков памяти в коде, сгенерированном компилятором, и большинство реализаций библиотеки / memcpy / memset используют SSE / SSE2. Кроме того, оптимизированное сохранение / восстановление с поддержкой аппаратного обеспечения теперь - вещь ( xsaveopt / xrstor), поэтому «надежное» сохранение / восстановление FPU может фактически сделать меньше работы, если некоторые / все регистры FP на самом деле не работают были использованы. например сохраняйте только низкие 128b регистров YMM, если они были обнулены с vzeroupper, поэтому CPU знает, что они чисты. (И отметьте этот факт только одним битом в формате сохранения.)

С «нетерпеливым» переключением контекста инструкции FPU остаются включенными все время, поэтому плохой код ядра может повредить их в любое время.

5
ответ дан Peter Cordes 18 August 2018 в 14:47
поделиться
  • 1
    Статья, на которую вы ссылались, немного устарела. В частности, поддержка ленивого режима была полностью удалена из ядра. Таким образом, режим ожидания по умолчанию - это единственный режим. – Hadi Brais 30 April 2018 в 17:29
  • 2

Не знаю, откуда такое восприятие. Но ядро ​​выполняется на том же процессоре, что и код режима пользователя, и поэтому имеет доступ к одному набору команд. Если процессор может работать с плавающей запятой (напрямую или сопроцессором), ядро ​​тоже может.

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

Мне любопытно, откуда взялось это восприятие? Может быть, я что-то упустил.

Нашел это . Кажется, это хорошее объяснение.

2
ответ дан Community 18 August 2018 в 14:47
поделиться
  • 1
    Возможно, мой вопрос немного вводит в заблуждение. Я понимаю, что FPU может выполнять эти инструкции с плавающей запятой (т.е. сам машинный код является системным агностиком), но я смущен тем, как скомпилировать код C без ошибок GCC о неопределенных символах, таких как __fixunssfsi, когда я Компиляция модуля ядра. Я подозреваю, что это просто GCC в зависимости от вспомогательных подпрограмм в библиотеке, из которой исключается ядро, поэтому как мне обойти это, чтобы создать правильный машинный код - процессор поддерживает с плавающей запятой. – Vilhelm Gray 8 April 2013 в 18:47
  • 2
    Позвольте мне добавить, что я знаю, что регистры с плавающей запятой не сохраняются; Я не особо забочусь об этом об ошибке с помощью программы пользовательских программ, так как я просто экспериментирую с кодом, чтобы лучше понять поведение. – Vilhelm Gray 8 April 2013 в 18:50
  • 3
    Я понял; Мне нужно передать этот флаг компилятора в GCC: -mhard-float. – Vilhelm Gray 8 April 2013 в 19:13
  • 4
    Дело в том, что состояние с плавающей запятой неправильно сохранено & amp; восстанавливается изнутри ядра, например. при планировании задач (он сохраняется только с точки зрения внутреннего приложения). – Basile Starynkevitch 8 April 2013 в 19:32

Ядро ОС может просто отключить FPU в режиме ядра.

В то время как операция FPU, в то время как ядро ​​с плавающей запятой включит FPU, и после этого выключите FPU.

Но вы не можете распечатать его.

1
ответ дан Rajeev Kumar 18 August 2018 в 14:47
поделиться

Не делайте этого!

В режиме ядра FPU отключен по нескольким причинам:

  • Он позволяет Linux работать в архитектурах, которые не имеют FPU
  • Он не позволяет сохранять и восстанавливать весь набор регистров каждый переход ядра / пользователя-пространства (это может удвоить время переключения контекста)
  • В основном все функции ядра используют целые числа для представления десятичных чисел -> вам, вероятно, не нужна плавающая точка
  • В Linux преемственность отключается, когда пространство ядра работает в режиме FPU
  • Число плавающей запятой является злым и могут создавать очень плохое неожиданное поведение

Если вы действительно хотите использовать номера FP (и не должны), вы должны использовать kernel_fpu_begin и kernel_fpu_end примитивы, чтобы избежать разбиения регистров пользовательского пространства, и вы должны учитывать все возможные проблемы (включая безопасность) при работе с номерами FP.

3
ответ дан RicoRico 18 August 2018 в 14:47
поделиться
Другие вопросы по тегам:

Похожие вопросы: