Мои заметки про процессоры для cовсем маленьких

в 7:15, , рубрики: amd64, x86, x86_64, архитектура процессора, простыми словами, процессор, строение процессора, схема процессора

Вступление

Центральный процессор (CPU, Central Processing Unit) — это основной компонент устройств, который выполняет все вычисления и логические операции, необходимые для работы программ.

Здесь я постараюсь рассказать про строение и работу процессора на примере x86–64 архитектуры.

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

Я решил поделиться своими заметками, так как возможно кому-то это может показаться полезным. На детальность информации не претендую, но не против конструктивной критики.

Вот довольно неплохие видео, которые noob friendly:

1) https://www.youtube.com/watch?v=ubsZ9MO9qkU

2) https://www.youtube.com/watch?v=aNVMpiyeY_U&t=280s

Устройство процессора (схематически)

Процессор

Процессор
Ядро
Ядро

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

Процесс обработки данных процессором (при прочтении поглядывай на картинки)

Когда процессору требуются данные, он сначала проверяет их в регистрах, и кэшах L1-L3.

Если он не находит данные там, то он генерирует сигнал на шине адреса, указывающий физический адрес памяти ОЗУ, по которому нужно прочитать данные. Этот сигнал на шине адреса генерируется процессором и передается контроллеру памяти.

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

То есть отправляем в ОЗУ адрес по шине адреса, а получаем данные по шине данных.

По шине данных информация помещается в кэш L3.

В отдельное ядро данные попадают с общего кэша L3, затем передаются в L2 кэш внутри каждого физического ядра процессора, и окончательно попадают в L1 кэш.

Кэш L1 часто разделяется на две части: кэш данных и кэш инструкций. Инструкции, которые передаются вместе с данными, попадают в кэш инструкций, а данные оказываются в кэше данных.

После того как данные и инструкции попадают в соответствующие кэши L1, инструкции отправляются в декодер.

Декодер ответственен за преобразование машинных инструкций в управляющие сигналы, которые определяют следующие операции процессора.

Данные из кэша данных могут попадать в буферы поступления данных (load buffers) для последующей обработки.

А декодированные инструкции направляются в буфер переупорядочевания (reorder buffer), который решает проблемы конфликтов данных и зависимостей инструкций, и может временно изменять порядок выполнения инструкций, чтобы обеспечить корректность выполнения программы.

Затем планировщик направляет инструкции в исполнительные или вычислительные блоки (ALU, FPU, VPU и так далее)

Планировщик определяет порядок выполнения операций, обеспечивая оптимальное использование ресурсов процессора, а также может распараллеливать операции для повышения производительности. Устройство управления координирует выполнение инструкций и управляет передачей данных между различными частями процессора.

Каждое ядро может обрабатывать несколько потоков инструкций (обычно 2) за счёт технологий Hyper-Threading (HT) и Simultaneous Multi-Threading (SMT). С работой в несколько потоков потом разберёмся на концепции многопоточности из Go, а сейчас подробнее про процессоры по основным моментам, которые важно понимать.

Данные из буферов поступления обычно загружаются в регистры общего назначения (если требуется).

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

После выполнения операций, предписанных инструкциями, преобразованные данные могут быть временно сохранены в регистрах, а затем перемещены обратно в кэш L1.

Далее данные перемещаются в иерархии выше: в кэш L2, а затем в кэш L3.

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

Описание компонентов

Устройство управления (Control Unit)

Управляет выполнением инструкций, координируя работу других компонентов.

Устройство управления включает в себя несколько ключевых компонентов, которые координируют функционирование процессора. Вот некоторые из них:

  1. Декодер инструкций: Отвечает за декодирование машинных инструкций, переводя их из машинного кода в управляющие сигналы, которые указывают, какие операции должны быть выполнены.

  2. Генератор сигналов управления: Создает сигналы, которые управляют различными функциями процессора, такими как чтение/запись в регистры, выбор источников данных для выполнения операций, управление памятью и так далее.

  3. Счётчик инструкций: Отслеживает текущую выполняемую инструкцию и управляет последовательностью исполнения инструкций внутри процессора.

  4. Управляющие регистры: Хранят состояние процессора, включая информацию о текущей инструкции, режиме работы процессора и т. д.

  5. Предсказатель переходов: В некоторых процессорах устройство управления включает предсказатель переходов, который анализирует последовательность инструкций для предсказания условных переходов, что улучшает предсказуемость ветвлений и оптимизирует выполнение программ.

Планировщик (Scheduler):

  • Планировщик отвечает за распределение микроопераций (micro-ops) между различными исполнительными блоками процессора, такими как ALU и FPU.

  • Он определяет порядок исполнения микроопераций, стараясь максимально эффективно использовать ресурсы процессора и минимизировать время простоя.

ALU (Arithmetic Logic Unit):

  • Выполняет арифметические операции (сложение, вычитание и т.д.).

  • Выполняет логические операции (AND, OR, XOR и т.д.).

FPU (Floating Point Unit):

Выполняет операции с плавающей запятой.

VPU (Vector Processing Unit):

Выполняет операций с векторными данными.

MMU (Memory Management Unit):

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

NPU (Neural Processing Unit):

Устройство, специализированное на выполнении операций, связанных с нейронными сетями и искусственным интеллектом. (добавил из-за хайпа)

Регистры:

Материал (англ): https://wiki.osdev.org/CPU_Registers_x86

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

Триггер (flip-flop) — это элементарная цифровая ячейка, способная хранить один бит информации (0 или 1).

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

Регистры делятся на регистры общего назначения и регистры специального назначения.

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

Регистры специального назначения обычно участвуют в управлении процессором, кэш-памятью, управлении памятью или других системных функциях. Хотя программисты обычно не работают напрямую с этими регистрами при написании прикладного программного обеспечения, они могут управлять ими через соответствующие инструкции или API для выполнения определенных задач.

В регистре может быть любое количество триггеров, в зависимости от его дизайна и предназначения.

Регистры общего назначения имеют размер, соответствующий разрядности процессора. А регистры специального назначения уже зависят от реализации того или иного процессора. Например, в некоторых архитектурах регистр управления кэш-памятью может быть 64-битным даже в 32-битных процессорах.

Регистры общего назначения:

  1. RAX (Accumulator Register):

    Используется для арифметических, логических операций и операций ввода-вывода.

  2. RBX (Base Register):

    Используется для хранения данных и может использоваться как базовый регистр при адресации памяти.

  3. RCX (Counter Register):

    Используется в операциях цикла и как счетчик в командах REP и LOOP.

  4. RDX (Data Register):

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

  5. RSI (Source Index):

    Используется для указания на исходный адрес при операциях с памятью (например, при копировании данных).

  6. RDI (Destination Index):

    Используется для указания на адрес назначения при операциях с памятью.

  7. R8-R15 (Дополнительные регистры):

    Введены в 64-разрядной архитектуре, используются для хранения данных и промежуточных значений.

Специальные регистры:

  1. RSP (Stack Pointer):

    Указывает на вершину стека.

  2. RBP (Base Pointer):

    Используется для доступа к параметрам и локальным переменным в стеке.

  3. CS (Code Segment):

    Хранит базовый адрес сегмента кода в ОЗУ, откуда процессор считывает команды для выполнения. Если проводить аналогию между кодом и книгой, то CS указывает на номер страницы в книге (программе)

  4. RIP (Instruction Pointer):

    Указывает на следующую инструкцию, которую должен выполнить процессор. Если проводить аналогию между кодом и книгой, то RIP указывает на номер строки на странице, которая указана в CS.

  5. FLAGS (или RFLAGS в 64-разрядной системе):

    Хранит флаги состояния процессора, отражают состояние различных аспектов выполнения инструкций процессора, таких как результаты арифметических операций, наличие переноса, знак числа, флаги нуля, переполнения и др. Они предназначены для управления логикой выполнения программы, исходя из текущего состояния выполнения инструкций процессора.

«Регистры в регистрах» ©Xzibit

Ранее существовали 16-битные регистры, такие как AX, BX, CX и DX, каждый из которых мог быть разделён на два 8-битных регистра: старший и младший. Например, регистр AX разделён на AH (старший байт) и AL (младший байт).

При расширении до 32 бит, 16-битные регистры стали частью новых 32-битных регистров. Например, регистр AX был включён в регистр EAX, где младшие 16 бит EAX соответствуют регистру AX.

При переходе к 64-битным регистрациям, 32-битные регистры (например, EAX) включены в более широкий регистр — RAX. В случае регистра RAX, младшие 32 бита этого регистра являются регистром EAX.

Таким образом, 8-битные регистры AH и AL всё также сохраняют свою функциональность и доступны в составе более широких регистров новых архитектур.

Сделано это в основном для обеспечения обратной совместимости.

; Использование регистра RAX для арифметических операций
    mov rax, 5          ; Кладем 5 в регистр RAX
    add rax, 10         ; Добавляем 10 к регистру RAX (итог: 15)

; Использование регистра RBX для хранения данных и арифметических операций
    mov rbx, rax        ; Копируем значение RAX в RBX (RBX теперь 15)
    sub rbx, 3          ; Вычитаем 3 из RBX (итог: 12)

; Использование регистра RCX в качестве счетчика
    mov rcx, 3          ; Кладем 3 в регистр RCX
loop_start:
    dec rcx             ; Уменьшаем RCX на 1
    jz loop_end         ; Если RCX = 0, переходим на метку loop_end
    add rax, rcx        ; Добавляем RCX к RAX (2 + 15, затем 1 + 17, в итоге RAX хранит 18)
    jmp loop_start      ; Переходим на метку loop_start
loop_end:

; Использование регистра RDX для умножения и деления
    mov rdx, 2          ; Кладем 2 в регистр RDX
    imul rax, rdx       ; Умножаем RAX на RDX (итог: 36)

Кэш-память:

  • L1: Первый уровень кэша, самый быстрый и маленький по объему.

  • L2: Второй уровень кэша, более медленный, но большего объема.

  • L3: Третий уровень кэша, ещё более медленный, но ещё большего объема, чем L2 и быстрее оперативной памяти. L3 кэш обычно является общим для всех ядер в многоядерном процессоре. Это означает, что все ядра процессора могут иметь доступ к данным, хранящимся в L3 кэше, что полезно для снижения задержек в доступе к данным между ядрами.

  • L4 — Тоже самое что и L3, но ещё медленнее (но все равно быстрее оперативной памяти) и объёмнее, используется только в высокопроизводительных системах, например, серверах.

Системная шина

Шина представляетсобой физический канал передачи данных и управляющих сигналов между различными компонентами компьютера. Все данные между процессором, регистрами, памятью и I/O‑устройствами (устройствами input‑output) передаются по шинам. Чтобы загрузить в память только что обработанные данные, процессор помещает адрес в шину адреса и данные в шину данных. Потом нужно дать разрешение на запись на шине управления.

Шина данных (Data Bus):

  • Шина данных используется для передачи фактических данных (байтов, слов и т. д.) между процессором и памятью, периферийными устройствами или другими компонентами внутри процессора.

  • Ширина шины данных (data bus width) измеряется в битах и часто соответствует разрядности процессора. Это количество бит, которые могут передаваться по шине данных одновременно. Например, 64-битная шина данных может передавать 64 бита данных за один раз.

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

Шина адреса (Address Bus):

  • Шина адреса используется для передачи адреса, по которому нужно прочитать или записать данные в памяти или других устройствах.

  • Ширина шины адреса определяет диапазон адресов, который процессор может обращаться. Например, 32-битная шина адреса позволяет обращаться к 2^32 (4 ГБ) адресам.

Шина управления (Control Bus):

  • Шина управления используется для передачи управляющих сигналов и команд между различными компонентами процессора и другими устройствами.

  • Она включает сигналы для чтения/записи данных, инициализации операций, сигналы прерывания и другие управляющие сигналы, необходимые для координации работы системы.

Тактирование процессора

Быстродействие компьютера определяется тактовой частотой его процессора.

Один такт процессора (или тактовый цикл) — это единица времени, определяемая одним импульсом тактового сигнала процессора.

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

Тактовая частота — количество тактов за секунду.

Частота нынешних процессоров измеряется в ГГц (Гигагерцы). 1 ГГц = 10⁹ Гц — миллиард тактов.

Продолжительность одного такта определяется тактовой частотой процессора. Например, если процессор работает на частоте 3 ГГц, это означает, что один такт длится примерно 0.33 наносекунды (1 / 3,000,000,000 секунды).

За один такт может выполняться одна или несколько элементарных операций. Одна сложная инструкция, такая как умножение или обращение к памяти, может потребовать несколько тактов для завершения

Чтобы уменьшить время выполнения программы, нужно либо оптимизировать (уменьшить) её, либо увеличить тактовую частоту. У части процессоров есть возможность увеличить частоту (разогнать процессор), однако такие действия физически влияют на процессор и нередко вызывают перегрев и выход из строя.

Выполнение инструкций

Инструкции хранятся в ОЗУ в последовательном порядке. Для гипотетического процессора инструкция состоит из кода операции и адреса памяти/регистра. Внутри управляющего устройства есть два регистра инструкций, в которые загружается код команды и адрес текущей исполняемой команды. Ещё в процессоре есть дополнительные регистры, которые хранят в себе последние 4 бита выполненных инструкций.

Ниже рассмотрен пример набора команд (на псевдокоде), который суммирует два числа:

  1. LOAD_A 8. Это команда сохраняет в ОЗУ данные, скажем, <1100 1000>. Первые 4 бита — код операции, следую. Именно он определяет инструкцию. Эти данные помещаются в регистры инструкций УУ. Команда декодируется в инструкцию load_A — поместить данные 1000(последние 4 бита команды) в регистр A.

  2. LOAD_B 2. Ситуация, аналогичная прошлой. Здесь помещается число 2 (0010) в регистр B.

  3. ADD B A. Команда суммирует два числа (точнее прибавляет значение регистра B в регистр A). УУ сообщает АЛУ, что нужно выполнить операцию суммирования и поместить результат обратно в регистр A.

  4. STORE_A 23. Сохраняем значение регистра A в ячейку памяти с адресом 23.

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

Кэш

Для начала примем один факт - с ростом объёма кэша возрастает и задержка доступа.

Причина в увеличении времени на определение пути к нужной ячейке памяти внутри большого кэша. Поэтому по скорости L1 > L2 > L3 > ОЗУ, но с объёмом ровно наоборот. Такая иерархия позволяет процессоре работать в балансе между скоростью и доступным объемом памяти.

У процессора есть механизм сохранения инструкций в кэш. Как мы выяснили ранее, за секунду процессор может выполнить миллиарды инструкций. Поэтому если бы каждая инструкция хранилась в ОЗУ, то её изъятие оттуда занимало бы больше времени, чем её обработка. Поэтому для ускорения работы процессор хранит часть инструкций и данных в кэше для ускорения доступа к наиболее часто используемым инструкциям и данным.

Строка или кэш-линия в контексте кэш-памяти (cache line) — это последовательная группа байтов. Она представляет собой минимальную единицу данных, которую кэш может загружать из основной памяти и сохранять. Длина строки обычно составляет 32, 64 или 128 байт, в зависимости от архитектуры процессора и кэш-памяти.

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

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

Подробнее

Грязные биты (dirty bits) — это флаги в кэш-памяти, которые указывают на то, что данные в кэше были изменены (модифицированы) по сравнению с их копией в основной памяти (ОЗУ). Когда данные в кэше и основной памяти не совпадают, они считаются «грязными».

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

Грязные биты играют важную роль в системах кэширования, помогая управлять синхронизацией данных между кэшем и основной памятью.

Поток инструкций

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

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

Если процессор многоядерный, это означает, что фактически в нём находятся несколько отдельных процессоров с некоторыми общими ресурсами, например кэшем.

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

Разрядность процессора

Основные аспекты разрядности процессора

Ширина данных:

  • Разрядность процессора указывает на количество бит, которые процессор может обработать за один цикл. Например, 32-битный процессор может работать с 32-битными числами или адресами памяти. То есть длина слова будет 32 бита.

  • Машинное слово — это единица данных, с которой процессор работает за один такт. Длина слова соответствует разрядности процессора. Термин «слово» пришел из ранней компьютерной науки и электроники.

Адресное пространство:

Разрядность также определяет максимальный объем памяти, который процессор может адресовать. 32-битный процессор может адресовать до 4 ГБ оперативной памяти (2^32 байт), тогда как 64-битный процессор может адресовать гораздо больше — до 16 эксабайт (2^64 байт), хотя современные операционные системы и аппаратное обеспечение обычно ограничивают этот объем до нескольких терабайт.

Регистр процессора:

Разрядность процессора определяет размер регистров. В 32-битном процессоре регистры имеют размер 32 бита, в 64-битном — 64 бита (касается не всех регистров, а по большей части регистров общего назначения).

Примеры разрядности процессоров

  1. 8-битные процессоры:

    Ранние процессоры, такие как Intel 8080 и MOS Technology 6502, имели разрядность 8 бит. Они могли обрабатывать данные по 8 бит за раз и имели ограниченные возможности по адресации памяти.

  2. 16-битные процессоры:

    Процессоры, такие как Intel 8086, были 16-битными. Они могли обрабатывать данные по 16 бит за раз и поддерживали до 64 КБ оперативной памяти.

  3. 32-битные процессоры:

    Процессоры, такие как Intel 80386 и более поздние модели, были 32-битными. Они могли обрабатывать данные по 32 бит за раз и адресовать до 4 ГБ оперативной памяти.

  4. 64-битные процессоры:

    Современные процессоры, такие как Intel Core и AMD Ryzen, имеют разрядность 64 бита. Они могут обрабатывать данные по 64 бит за раз и поддерживают адресацию значительно больших объемов памяти.

Почему x86?

  1. Intel 8086 (1978 год): Этот процессор был первым 16-битным процессором в семействе x86, который заложил фундамент архитектуры x86.

  2. Intel 80386 (1985 год): Этот процессор продолжил развитие архитектуры x86 и добавил поддержку 32-битной адресации и данных. Он был очень важным для современных операционных систем и приложений, так как открыл путь к полноценной поддержке многозадачности и сложных вычислений.

  3. AMD Opteron (2003): В 2003 году AMD представила процессоры с архитектурой x86-64 (также известной как AMD64), которые поддерживают 64-битные вычисления, но также и имеют обратную совместимость с x86, поэтому такое название: x86-64. Хотя до сих пор принято называть такие процессоры x86-ыми. Intel просто лицензировали технологию AMD, чуть-чуть доработали, обозвали EM64T, но по сути это та же AMD64, поэтому под архитектуру x86-64 программы обычно обозначаются обычно как AMD64.

ARM

История и развитие ARM процессоров

  1. Основание и начало: ARM была разработана в 1980-х годах компанией Acorn Computers Ltd (Великобритания) в ответ на необходимость компактных, энергоэффективных и высокопроизводительных процессоров для использования в персональных компьютерах.

  2. Переход в мобильные устройства: В конце 1980-х и начале 1990-х годов ARM начала активно использоваться в мобильных устройствах, таких как КПК и мобильные телефоны. Эта архитектура была особенно привлекательной из-за своей энергоэффективности, что способствовало продолжительности работы от аккумулятора.

  3. Эволюция и расширение применения: С течением времени ARM стала доминирующей архитектурой в мобильной индустрии. Процессоры ARM были адаптированы для использования в различных устройствах, от смартфонов и планшетов до IoT устройств и встраиваемых систем.

  4. Развитие в вычислительных системах: В последние годы ARM также начала проникать в область серверных решений с целью предложить более энергоэффективные альтернативы для центров обработки данных (ЦОД). Это направление включает в себя процессоры, такие как ARMv8-A, которые поддерживают 64-битные вычисления и являются конкурентами для x86-64 в сфере серверных вычислений.

RISC (Reduced Instruction Set Computing)

Преимущества:

  1. Простота инструкций: Инструкции в RISC-архитектуре проще и быстрее выполняются, что позволяет увеличить производительность за счет более быстрого цикла выполнения команд.

  2. Конвейеризация: За счет простоты инструкций конвейеризация (pipelining) становится более эффективной, что позволяет выполнить больше команд за такт.

  3. Энергоэффективность: Меньшее количество транзисторов и простота инструкций обычно приводят к более низкому энергопотреблению, что важно для мобильных и встроенных устройств.

  4. Универсальность: Простые инструкции легко реализуются в аппаратуре, что делает архитектуру более гибкой для различных типов приложений.

Недостатки:

  1. Больший объем кода: Простота инструкций может привести к увеличению объема кода, так как для выполнения сложных операций требуется больше команд.

  2. Сложности компиляции: Компиляторы должны быть более умными, чтобы эффективно использовать простые инструкции для сложных задач.

CISC (Complex Instruction Set Computing)

Преимущества:

  1. Сложные инструкции: Более сложные и мощные инструкции могут выполнять сложные операции за один шаг, что упрощает программирование и уменьшает объем кода.

  2. Наследие: x86-64 имеет огромное наследие программного обеспечения и оборудования, что делает его по-прежнему популярным в корпоративной среде, на настольных ПК и в серверах.

    Многие предприятия и конечные пользователи зависят от программ, которые написаны и оптимизированы под x86-64.

  3. Многофункциональность: Поддержка множества инструкций и режимов работы позволяет процессорам x86-64 быть очень универсальными и адаптивными для разных задач.

Недостатки:

  1. Сложность и энергопотребление: Более сложные инструкции требуют больше транзисторов и более сложной логики, что увеличивает энергопотребление и тепловыделение.

  2. Наследие: Поддержка устаревших инструкций и режимов может накладывать дополнительные ограничения на архитектуру, делая ее менее эффективной по сравнению с RISC.

ARM vs x86

Исходя из сухих данных, развитие ARM архитектуры может обеспечить ей доминирование на рынке по производительности/энергоэффективности, тем более, что даже Apple Silicon M4 вполне себе способна конкурировать с некоторыми современными десктопными процессорами.

Но наследие ПО под x86 заставляет нас оставаться на довольно древней и далеко не идеальной x86-64 архитектуре.

Автор: daniil_kulikov

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js