Desarrollo de un shell basado en Linux

¿Qué es una concha?

Es la parte visible de un sistema operativo con la que interactúan los usuarios, los usuarios interactúan con el sistema operativo proporcionando comandos al shell, que a su vez interpreta estos comandos y los ejecuta.

La siguiente imagen muestra el proceso de ejecución simplificado, en el que el shell recibe la entrada, la
pasa al analizador léxico (se discutirá en detalle) que creará tokens, luego la salida del analizador léxico se pasará a un analizador que lo verifica. para errores de sintaxis y ejecuta las acciones semánticas asignadas (esto crea la tabla de comandos), y finalmente, cuando el analizador llega a cierto punto, la tabla se ejecutará.

El shell se implementará en 3 componentes como se muestra en el siguiente diagrama de arquitectura:

Architecture diagram

1. Analizador léxico
La primera parte del análisis de entrada es la etapa de análisis léxico donde la entrada se lee carácter por carácter para formar tokens, usaremos un comando llamado lex para construir nuestro archivo, en este archivo definiremos nuestro patrón seguido de the: token name, el analizador léxico leerá la entrada carácter por carácter y cuando el patrón coincida con la string de la izquierda, se convertirá a la string de la derecha.
ex:

Command input: ls -al

El analizador leerá l y luego formará un token llamado PALABRA, luego leerá – y caracteres (al) y formará una OPCIÓN, la salida será OPCIÓN DE PALABRA, esta salida se pasará al analizador para verificar si hay un error de sintaxis.

1. “#” : IO
2. [ 1 ]?”>” : IO
3. “” : IO
5. [ 1]?”>>” : IO
6. [ 1-2]”>&”[1- 2 ] : IOR
7. “|” : TUBO
8. “&” : AMPERSAND
9. [ ]”-“[a-zA-Z0-9]* : OPCIÓN
10. [ ]”–“[a-zA-Z=a-zA-Z]* : OPCION2
11. \%\=\+\’\”\(\)\$\/\_\-\.\?\*\~a-zA-Z0-9]+ : PALABRA

La gramática anterior consta de 11 tokens que estos tokens forman cuando la entrada cumple con la descripción del token.
El token de IO está formado por un carácter # o un ‘>’ que podría estar precedido por el número uno (máximo una vez) o ‘ que presentamos como un nuevo token que reemplaza el token de redirección de error, otra forma de IO es usar ‘> >’ que puede estar precedido por el número uno (máximo una vez) y finalmente ‘>&’ que es un IOR y puede estar precedido y/o seguido por uno o dos.

Los tokens de tubería y ampersand se forman en ‘|’ y ‘&’ respectivamente, el token de opción se forma cuando hay un guión precedido por un espacio y seguido por cualquier carácter alfabético o número.
El token option2 se forma cuando hay dos guiones precedidos por un espacio y seguidos por cualquier carácter alfabético.
El WORD Token podrá estar formado por caracteres alfabéticos, números y los siguientes caracteres %, =, +, ‘, “, (, ), $, /, _, -, ., ?, *, ~

2. Analizador
Una vez que se han formado los tokens a partir de la entrada, los tokens pasan como un flujo al analizador, que analiza la entrada para detectar errores de sintaxis y ejecutar las acciones semánticas asignadas. Se puede pensar en un analizador como la gramática y la sintaxis del lenguaje (que define cómo se verán nuestros comandos como aceptables), usaremos un comando llamado yacc para compilar la gramática, construiremos la gramática como una forma de estados que facilita la construcción y el despliegue de la gramática.

A continuación se muestra nuestra definición de gramática:

1. q00: NUEVA LÍNEA {retornar 0;} | cmd q1 q0 | error;
2. q0: NUEVA LINEA {retornar 1;} | TUBO q00 {clrcont;};
3. q1: opción q2 | opción opción q2 | lista_arg q3 | io_modificador q4 | fondo q5 | io_descr q3 | /*vacío*/ {InsertNode(); clrcont();};
4. q2: lista_arg q3 | io_modificador q4 | io_descr q3 | fondo q5 | /*vacío*/ {InsertNode(); clrcont();};
5. q3: io_modificador q4 | io_descr q3 | fondo q5 | /*vacío*/ {InsertNode(); clrcont();};
6. q4: archivo q3 ;
7. cmd: PALABRA {cmad.cmd = yylval.str;};
8. lista_arg:arg | arg lista_arg;
9. arg: PALABRA {insertArgNode(yylval.str);};
10. archivo: PALABRA {io_red(yylval.str);};
11. io_modifier: IO {cmad.op=yylval.str;};
12. io_descr: IOR {cmad.op=yylval.str;};
13. opción: OPCIÓN {cmad.opt = yylval.str;} | OPCIÓN2 {cmad.opt2 = yylval.str;};
14. fondo: AMPERSAND {bg = ‘1’;};
15. q5: /*vacío*/{InsertarNode(); clrcont();};

La gramática anterior especifica los diferentes estados del proceso de análisis,

El analizador comienza desde el estado q00 y analiza hasta llegar a uno de los estados q5, q3, q1, lo que sucede de manera inversa debido a la técnica de análisis en uso (análisis de abajo hacia arriba), la gramática reduce los tokens en función de su ubicación, Word puede se reduce a cmd si aparece al principio, arg_list si aparece después de un comando, file si aparece después de una redirección, luego la oración se analiza de acuerdo con la gramática, comenzando desde el estado q00, el analizador se mueve al estado q1 leyendo un cmd , en el estado q1 si el analizador lee una opción, la oración puede tener uno de los siguientes argumentos, IO o fondo después, o no tener nada, si el analizador lee argumentos, la oración solo puede tener redirección después, si el analizador lee un ampersand debería haber ser nada después.

Luego, el proceso comienza de nuevo cuando el analizador lee una tubería, esto permite que múltiples comandos simples se conecten mediante tuberías para formar un comando complejo.
Definimos un comando simple como cualquier comando que consiste en un comando, opciones, argumentos y/o redirección de E/S.
La combinación de múltiples comandos simples usando tuberías da como resultado una estructura que llamamos comando complejo.

Las acciones semánticas asociadas con la gramática construyen la tabla de análisis y asignan los valores de comando a la estructura de datos que, después de construir la tabla de comandos, se envían al ejecutor.
La tabla de comandos consta de filas de comandos simples y estas filas se forman a partir de un comando complejo conectado por conductos, una entrada de Comando simple que contiene el nombre del comando que se ejecutará, opciones que son las opciones que se ejecutarán con el comando, argumentos que contienen los argumentos que deben pasarse al comando, entrada estándar (stdIn) que especifica la ubicación desde la que el comando obtendrá su entrada de manera predeterminada, es la terminal a menos que se especifique lo contrario en el comando, salida estándar (stdOut) especifica el lugar donde se encuentra el comando imprimirá la salida de ejecución y, de forma predeterminada, es la terminal, el error estándar (stdError) especifica el lugar en el que el comando imprimirá los mensajes de error de ejecución y, de forma predeterminada, es la terminal a menos que el usuario la redireccione.

La gramática construida permite la siguiente sintaxis:

syntax

Lo que permite un comando con opciones, argumentos, redirección de IO y ser un proceso en segundo plano (&). un comando con cualquiera de los anteriores es un comando simple cuando conectamos varios comandos simples formamos un comando complejo.

Mientras analiza el comando, nuestro analizador guarda los detalles del comando en nuestra tabla para pasarlos al ejecutor.

Elegimos una tabla para que sea nuestra estructura de datos, necesitamos almacenar la siguiente información sobre cada comando, Command, Option, Option2, Arguments, StdIn, StdOut, StdError.

Por ejemplo:

ls –al | sort -r

Este comando dará como resultado la siguiente tabla (cada fila es un comando simple, la tabla en sí es un comando complejo).

Table

3.Ejecutor

Una vez que se ha creado la tabla de comandos, el ejecutor es responsable de crear un proceso para cada comando en la tabla y manejar cualquier redirección si es necesario.
El ejecutor itera sobre la tabla para ejecutar cada comando simple y conectarlo con el siguiente en cada entrada de la tabla (comando simple) el ejecutor ordena que el comando, la opción y los argumentos se pasen a la función execvp que reemplaza el proceso de invocación actual con el llamado, la función execvp como primer parámetro recibe el nombre del archivo a ejecutar y una array terminada en nulo que contiene las opciones (si las hay) seguidas de argumentos.

Pero antes de que se llame al execvp, el Ejecutor maneja la redirección en el shell, si el comando está precedido por un comando, esto significa que hay una tubería antes y, por lo tanto, la entrada del comando está configurada para recibirse de la tubería anterior, luego se verifica el comando para cualquier redirección de entrada que, si existe, sobrescribe la entrada de la tubería anterior, si el comando no está precedido por un comando, entonces no hay tubería (comando simple), de lo contrario (más de un comando simple), la salida del comando se envía al siguiente comando en la tabla, el comando se verifica para la redirección de salida si hay una redirección de entrada, la entrada del archivo asignado sobrescribe la entrada del comando anterior.

Una vez que se ha manejado la redirección, se verifica el indicador de fondo del comando que indica si el shell debe esperar que el comando termine la ejecución o enviar el proceso para que se ejecute en segundo plano, ahora para que el ejecutor ejecute el comando tiene que crea una imagen del shell para que se ejecute, el ejecutor bifurca el proceso actual (shell) y ejecuta el comando en el elemento secundario de este fork.

El ejecutor comienza ejecutando la primera fila, configurando la salida del comando a la salida estándar, luego sobrescribiendo la salida en la tubería para que la reciba el segundo comando, después de que el primer comando (ls –al) ejecuta el segundo comando comienza a ejecutarse asignando la entrada para que se lea desde la entrada estándar al principio, luego, debido a que el comando está precedido por otro, la entrada está configurada para recibirse desde la tubería, y dado que el comando no contiene ninguna redirección de entrada (desde el archivo) la entrada estándar para el comando permanecerá en la tubería, la salida estándar del comando será en la pantalla, luego se verifica si el comando debe enviar su salida al siguiente comando, en este caso, este es el último comando, así que la salida no se sobrescribirá con tuberías,pero dado que el comando tiene una redirección de salida a un archivo, el archivo sobrescribirá la salida estándar.

Ejecutando el siguiente comando

ls –al | sort –r >file

La tabla que construye el analizador se verá así:

Table2

El código ejecutor iterará sobre esta tabla y realizará los pasos mencionados anteriormente y borrará todo cuando finalice el comando, para estar listo para recibir el siguiente comando.

Publicación traducida automáticamente

Artículo escrito por Yjaloudi y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *