GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cuál es la diferencia entre el espacio de usuario y el espacio del kernel?

¿Se utiliza el espacio de Kernel cuando Kernel se ejecuta en nombre del programa de usuario, es decir, System Call? ¿O es el espacio de direcciones para todos los subprocesos del Kernel (por ejemplo, el programador)?

Sí y sí.

Antes de continuar, debemos decir esto sobre la memoria.

La memoria se divide en dos áreas distintas:

  • El espacio de usuario , que es un conjunto de ubicaciones donde se ejecutan los procesos de usuario normales (es decir, todo lo que no sea el kernel). La función del kernel es administrar las aplicaciones que se ejecutan en este espacio para que no interfieran entre sí y con la máquina.
  • El espacio del kernel , que es la ubicación donde se almacena el código del núcleo y se ejecuta bajo.

Los procesos que se ejecutan en el espacio del usuario tienen acceso solo a una parte limitada de la memoria, mientras que el kernel tiene acceso a toda la memoria. Los procesos que se ejecutan en el espacio del usuario también no tener acceso al espacio del núcleo. Los procesos del espacio de usuario pueden solo acceder a una pequeña parte del kernel a través de una interfaz expuesta por el núcleo:las llamadas del sistema . Si un proceso realiza una llamada al sistema, se envía una interrupción de software al núcleo, que luego envía el controlador de interrupción apropiado y continúa su trabajo una vez que el controlador ha terminado.

El código de espacio del kernel tiene la propiedad de ejecutarse en "modo kernel", que (en su computadora típica de escritorio -x86-) es lo que llama código que se ejecuta bajo el anillo 0 . Por lo general, en la arquitectura x86, hay 4 anillos de protección . Anillo 0 (modo kernel), Anillo 1 (puede ser utilizado por hipervisores o controladores de máquinas virtuales), Anillo 2 (puede ser utilizado por controladores, aunque no estoy tan seguro de eso). El anillo 3 es bajo el que se ejecutan las aplicaciones típicas. Es el anillo con menos privilegios y las aplicaciones que se ejecutan en él tienen acceso a un subconjunto de las instrucciones del procesador. El anillo 0 (espacio del núcleo) es el anillo más privilegiado y tiene acceso a todas las instrucciones de la máquina. Por ejemplo, una aplicación "simple" (como un navegador) no puede usar instrucciones de ensamblaje x86 lgdt para cargar la tabla de descriptores globales o hlt para detener un procesador.

Si es el primero, ¿significa que el programa de usuario normal no puede tener más de 3 GB de memoria (si la división es 3 GB + 1 GB)? Además, en ese caso, ¿cómo puede el kernel usar la memoria alta, porque a qué dirección de memoria virtual se asignarán las páginas de la memoria alta, ya que 1 GB de espacio del kernel se asignará lógicamente?

Para obtener una respuesta a esto, consulte la excelente respuesta de wag aquí


Los anillos de CPU son la distinción más clara

En el modo protegido x86, la CPU siempre está en uno de los 4 anillos. El kernel de Linux solo usa 0 y 3:

  • 0 para núcleo
  • 3 para usuarios

Esta es la definición más dura y rápida de kernel vs userland.

Por qué Linux no usa los anillos 1 y 2:https://stackoverflow.com/questions/6710040/cpu-privilege-rings-why-rings-1-and-2-arent-used

¿Cómo se determina el timbre actual?

El timbre actual se selecciona mediante una combinación de:

  • tabla de descriptores globales:una tabla en memoria de entradas GDT, y cada entrada tiene un campo Privl que codifica el anillo.

    La instrucción LGDT establece la dirección en la tabla de descriptores actual.

    Ver también:http://wiki.osdev.org/Global_Descriptor_Table

  • el segmento registra CS, DS, etc., que apuntan al índice de una entrada en la GDT.

    Por ejemplo, CS = 0 significa que la primera entrada de la GDT está actualmente activa para el código de ejecución.

¿Qué puede hacer cada anillo?

El chip de la CPU está construido físicamente de modo que:

  • el anillo 0 puede hacer cualquier cosa

  • el anillo 3 no puede ejecutar varias instrucciones y escribir en varios registros, en particular:

    • no puede cambiar su propio anillo! De lo contrario, podría establecerse en el timbre 0 y los timbres serían inútiles.

      En otras palabras, no puede modificar el descriptor del segmento actual, que determina el timbre actual.

    • no se pueden modificar las tablas de páginas:https://stackoverflow.com/questions/18431261/how-does-x86-paging-work

      En otras palabras, no puede modificar el registro CR3 y la paginación en sí misma evita la modificación de las tablas de páginas.

      Esto evita que un proceso vea la memoria de otros procesos por razones de seguridad/facilidad de programación.

    • no puede registrar controladores de interrupción. Estos se configuran escribiendo en ubicaciones de memoria, lo que también se evita con la paginación.

      Los controladores se ejecutan en el anillo 0 y romperían el modelo de seguridad.

      En otras palabras, no puede usar las instrucciones LGDT y LIDT.

    • no puede hacer instrucciones IO como in y out y, por lo tanto, tener accesos de hardware arbitrarios.

      De lo contrario, por ejemplo, los permisos de archivo serían inútiles si cualquier programa pudiera leer directamente desde el disco.

      Más precisamente gracias a Michael Petch:en realidad es posible que el sistema operativo permita instrucciones IO en el anillo 3, esto en realidad está controlado por el segmento de estado de la tarea.

      Lo que no es posible es que el anillo 3 se dé permiso para hacerlo si no lo tenía en primer lugar.

      Linux siempre lo rechaza. Ver también:https://stackoverflow.com/questions/2711044/why-doesnt-linux-use-the-hardware-context-switch-via-the-tss

¿Cómo hacen la transición los programas y los sistemas operativos entre anillos?

  • cuando se enciende la CPU, comienza a ejecutar el programa inicial en el anillo 0 (bueno, pero es una buena aproximación). Puede pensar que este programa inicial es el núcleo (pero normalmente es un cargador de arranque que luego llama al núcleo aún en el anillo 0).

  • cuando un proceso de usuario quiere que el núcleo haga algo por él, como escribir en un archivo, usa una instrucción que genera una interrupción como int 0x80 o syscall para señalar el núcleo. x86-64 Linux syscall ejemplo de hola mundo:

    .data
    hello_world:
        .ascii "hello world\n"
        hello_world_len = . - hello_world
    .text
    .global _start
    _start:
        /* write */
        mov $1, %rax
        mov $1, %rdi
        mov $hello_world, %rsi
        mov $hello_world_len, %rdx
        syscall
    
        /* exit */
        mov $60, %rax
        mov $0, %rdi
        syscall
    

    compilar y ejecutar:

    as -o hello_world.o hello_world.S
    ld -o hello_world.out hello_world.o
    ./hello_world.out
    

    GitHub ascendente.

    Cuando esto sucede, la CPU llama a un controlador de devolución de llamada de interrupción que el kernel registró en el momento del arranque. Aquí hay un ejemplo baremetal concreto que registra un controlador y lo usa.

    Este controlador se ejecuta en el anillo 0, que decide si el núcleo permitirá esta acción, la realizará y reiniciará el programa de usuario en el anillo 3. x86_64

  • cuando el exec se utiliza la llamada al sistema (o cuando el núcleo se iniciará /init ), el núcleo prepara los registros y la memoria del nuevo proceso de espacio de usuario, luego salta al punto de entrada y cambia la CPU al anillo 3

  • Si el programa intenta hacer algo malo como escribir en un registro prohibido o en una dirección de memoria (debido a la paginación), la CPU también llama a algún controlador de devolución de llamada del núcleo en el anillo 0.

    Pero como el espacio de usuario era travieso, el kernel podría matar el proceso esta vez, o darle una advertencia con una señal.

  • Cuando el núcleo arranca, configura un reloj de hardware con una frecuencia fija, que genera interrupciones periódicamente.

    Este reloj de hardware genera interrupciones que ejecutan el timbre 0 y le permiten programar qué procesos de usuario se activan.

    De esta manera, la programación puede ocurrir incluso si los procesos no están realizando ninguna llamada al sistema.

¿Cuál es el punto de tener varios anillos?

Hay dos ventajas principales de separar kernel y userland:

  • es más fácil hacer programas ya que está más seguro de que uno no interferirá con el otro. Por ejemplo, un proceso de área de usuario no tiene que preocuparse por sobrescribir la memoria de otro programa debido a la paginación, ni por poner el hardware en un estado no válido para otro proceso.
  • es más seguro. P.ej. los permisos de archivo y la separación de memoria podrían evitar que una aplicación de piratería lea sus datos bancarios. Esto supone, por supuesto, que confías en el kernel.

¿Cómo jugar con él?

Creé una configuración completa que debería ser una buena manera de manipular los anillos directamente:https://github.com/cirosantilli/x86-bare-metal-examples

Desafortunadamente, no tuve la paciencia para hacer un ejemplo de espacio de usuario, pero llegué hasta la configuración de paginación, por lo que el espacio de usuario debería ser factible. Me encantaría ver una solicitud de extracción.

Alternativamente, los módulos del kernel de Linux se ejecutan en el anillo 0, por lo que puede usarlos para probar operaciones privilegiadas, p. lea los registros de control:https://stackoverflow.com/questions/7415515/how-to-access-the-control-registers-cr0-cr2-cr3-from-a-program-getting-segmenta/7419306#7419306

Aquí hay una configuración conveniente de QEMU + Buildroot para probarlo sin matar a su host.

La desventaja de los módulos del kernel es que otros kthreads se están ejecutando y podrían interferir con sus experimentos. Pero, en teoría, puede hacerse cargo de todos los controladores de interrupciones con su módulo kernel y poseer el sistema, en realidad sería un proyecto interesante.

Anillos negativos

Si bien los anillos negativos en realidad no se mencionan en el manual de Intel, en realidad hay modos de CPU que tienen más capacidades que el anillo 0 en sí mismo, por lo que encajan bien con el nombre de "anillo negativo".

Un ejemplo es el modo de hipervisor utilizado en la virtualización.

Para más detalles ver:

  • https://security.stackexchange.com/questions/129098/what-is-protection-ring-1
  • https://security.stackexchange.com/questions/216527/ring-3-exploits-and-existence-of-other-rings

BRAZO

En ARM, los anillos se denominan niveles de excepción, pero las ideas principales siguen siendo las mismas.

Existen 4 niveles de excepción en ARMv8, comúnmente utilizados como:

  • EL0:zona de usuario

  • EL1:kernel ("supervisor" en terminología ARM).

    Ingresado con el svc instrucción (SuperVisor Call), anteriormente conocida como swi antes del ensamblaje unificado, que es la instrucción utilizada para realizar llamadas al sistema Linux. Hola mundo ARMv8 ejemplo:

    hola.S

    .text
    .global _start
    _start:
        /* write */
        mov x0, 1
        ldr x1, =msg
        ldr x2, =len
        mov x8, 64
        svc 0
    
        /* exit */
        mov x0, 0
        mov x8, 93
        svc 0
    msg:
        .ascii "hello syscall v8\n"
    len = . - msg
    

    GitHub ascendente.

    Pruébelo con QEMU en Ubuntu 16.04:

    sudo apt-get install qemu-user gcc-arm-linux-gnueabihf
    arm-linux-gnueabihf-as -o hello.o hello.S
    arm-linux-gnueabihf-ld -o hello hello.o
    qemu-arm hello
    

    Aquí hay un ejemplo baremetal concreto que registra un controlador SVC y realiza una llamada SVC.

  • EL2:hipervisores, por ejemplo Xen.

    Ingresado con el hvc instrucción (Llamada de HyperVisor).

    Un hipervisor es para un sistema operativo lo que un sistema operativo es para el espacio del usuario.

    Por ejemplo, Xen le permite ejecutar varios sistemas operativos, como Linux o Windows, en el mismo sistema al mismo tiempo, y aísla los sistemas operativos entre sí por seguridad y facilidad de depuración, tal como lo hace Linux con los programas de usuario.

    Los hipervisores son una parte clave de la infraestructura de la nube actual:permiten que varios servidores se ejecuten en un solo hardware, manteniendo el uso del hardware siempre cerca del 100 % y ahorrando mucho dinero.

    AWS, por ejemplo, usó Xen hasta 2017 cuando su cambio a KVM fue noticia.

  • EL3:otro nivel más. Ejemplo de TODO.

    Ingresado con el smc instrucción (llamada en modo seguro)

El modelo de referencia de la arquitectura ARMv8 DDI 0487C.a - Capítulo D1 - El modelo del programador de nivel de sistema AArch64 - La figura D1-1 ilustra esto maravillosamente:

La situación de ARM cambió un poco con la llegada de ARMv8.1 Virtualization Host Extensions (VHE). Esta extensión permite que el kernel se ejecute en EL2 de manera eficiente:

VHE se creó porque las soluciones de virtualización en el kernel de Linux, como KVM, han ganado terreno sobre Xen (ver, por ejemplo, el cambio de AWS a KVM mencionado anteriormente), porque la mayoría de los clientes solo necesitan máquinas virtuales Linux y, como puede imaginar, estar todo en una sola proyecto, KVM es más simple y potencialmente más eficiente que Xen. Así que ahora el kernel host de Linux actúa como hipervisor en esos casos.

Tenga en cuenta cómo ARM, quizás debido al beneficio de la retrospectiva, tiene una mejor convención de nomenclatura para los niveles de privilegio que x86, sin necesidad de niveles negativos:0 es el más bajo y 3 el más alto. Los niveles más altos tienden a crearse con más frecuencia que los más bajos.

El EL actual se puede consultar con el MRS instrucción:https://stackoverflow.com/questions/31787617/what-is-the-current-execution-mode-exception-level-etc

ARM no requiere que todos los niveles de excepción estén presentes para permitir implementaciones que no necesitan la función para ahorrar área de chip. ARMv8 "Niveles de excepción" dice:

Es posible que una implementación no incluya todos los niveles de excepción. Todas las implementaciones deben incluir EL0 y EL1.EL2 y EL3 son opcionales.

QEMU, por ejemplo, tiene como valor predeterminado EL1, pero EL2 y EL3 se pueden habilitar con opciones de línea de comandos:https://stackoverflow.com/questions/42824706/qemu-system-aarch64-entering-el1-when-emulator-a53-power-up

Fragmentos de código probados en Ubuntu 18.10.


Si es el primero, ¿significa que el programa de usuario normal no puede tener más de 3 GB de memoria (si la división es 3 GB + 1 GB)?

Sí, este es el caso en un sistema Linux normal. Hubo un conjunto de parches "4G/4G" flotando en un punto que hizo que los espacios de direcciones del kernel y del usuario fueran completamente independientes (a un costo de rendimiento porque dificultaba que el kernel accediera a la memoria del usuario), pero no creo alguna vez se fusionaron aguas arriba y el interés disminuyó con el surgimiento de x86-64

Además, en ese caso, ¿cómo puede el kernel usar la memoria alta, porque a qué dirección de memoria virtual se asignarán las páginas de la memoria alta, ya que 1 GB de espacio del kernel se asignará lógicamente?

La forma en que Linux solía funcionar (y todavía lo hace en sistemas donde la memoria es pequeña en comparación con el espacio de direcciones) era que toda la memoria física se mapeaba permanentemente en la parte del núcleo del espacio de direcciones. Esto permitió que el kernel accediera a toda la memoria física sin reasignación, pero claramente no escala a máquinas de 32 bits con mucha memoria física.

Así nació el concepto de baja y alta memoria. La memoria "baja" se asigna permanentemente al espacio de direcciones del kernel. la memoria "alta" no lo es.

Cuando el procesador ejecuta una llamada al sistema, se ejecuta en modo kernel pero aún en el contexto del proceso actual. Por lo tanto, puede acceder directamente tanto al espacio de direcciones del kernel como al espacio de direcciones del usuario del proceso actual (suponiendo que no esté utilizando los parches 4G/4G mencionados anteriormente). Esto significa que no hay problema para que la memoria "alta" se asigne a un proceso de espacio de usuario.

El uso de memoria "alta" para propósitos de kernel es más problemático. Para acceder a la memoria alta que no está asignada al proceso actual, debe asignarse temporalmente al espacio de direcciones del núcleo. Eso significa código adicional y una penalización de rendimiento.


Linux
  1. Linux:¿diferencia entre el espacio del usuario y el espacio del kernel?

  2. ¿Diferencia entre el usuario de Sudo y el usuario raíz?

  3. pila de kernel y pila de espacio de usuario

  4. ¿Cuál es la diferencia entre fsck y e2fsck?

  5. ¿Cuál es la diferencia entre ls y l?

¿Cuál es la diferencia entre InnoDB y MyISAM?

¿Cuál es la diferencia entre los núcleos de macOS y Linux?

¿Cuál es la diferencia entre Linux y Unix?

¿Qué es un Hipervisor? ¿Cuál es la diferencia entre el tipo 1 y 2?

¿Cuál es la diferencia entre curl y Wget?

¿Cuál es la diferencia entre partx y kpartx?