WikiHow funciona como un wiki, lo que significa que muchos de nuestros artículos están escritos por varios autores. Durante la creación de este artículo, 90 personas, incluso de forma anónima, trabajaron para editarlo y mejorarlo.
Los sistemas operativos, compuestos por cientos de miles de líneas de código, permiten a los usuarios interactuar con equipo de computo. Suelen estar escritos en los lenguajes de programación C, C++ y ensamblador.
Pasos
- Para construir un sistema operativo usando código C o C++, por supuesto, usará un compilador u otro. Esto significa que debe leer el manual/instrucciones/documentación del compilador C/C++ seleccionado que viene incluido con el software o está disponible en el sitio web del distribuidor. Tendrás que aprender muchas cosas complejas sobre el compilador y también necesitarás aprender su diseño y ABI para mejorar C++. Se espera que comprenda los distintos formatos de tareas de ejecución (ELF, PE, COFF, binario normal, etc.) y tenga en cuenta que el formato nativo de Windows PE (.exe) está protegido por derechos de autor.
-
Seleccione Interfaz de programación de aplicaciones (API). Una selección de buenas API es POSIX, ya que está bien documentada. Todo sistemas unix tiene al menos soporte parcial para POSIX, por lo que sería trivial construir programas Unix en su sistema operativo.
Decidir sobre un diseño. Hay núcleos monolíticos y micronúcleos. Los núcleos monolíticos ejecutan todos los servicios del núcleo, mientras que los microkernels tienen un núcleo pequeño junto con una implementación personalizada de servicios. En general, los núcleos monolíticos son más rápidos, pero los micronúcleos tienen un mejor aislamiento y protección ante posibles fallos.
Considere el desarrollo y el trabajo en equipo. De esta manera, dedicará menos tiempo a resolver grandes problemas, lo que le permitirá crear un mejor sistema operativo en menos tiempo.
No borres tu disco duro por completo. Recuerde, formatear el disco borrará permanentemente todos sus datos. Utilice GRUB u otro administrador para duplicar el inicio de su computadora desde otro sistema operativo hasta que su versión esté completamente lista para funcionar.
Empiece poco a poco. Primero preste atención a las pequeñas cosas, como la visualización de texto y las interrupciones, antes de pasar a elementos complejos como la gestión de la memoria y la multitarea.
Mantenga una copia de seguridad de la última versión funcional. Esto proporciona cierta tranquilidad en caso de que algo salga completamente mal en la versión actual de su sistema operativo o en adiciones posteriores. Si su computadora se estropea y no puede arrancar, como usted mismo comprende, tener una segunda copia con la que trabajar será una excelente oportunidad para solucionar los problemas existentes.
Pruebe su nuevo sistema operativo para máquina virtual. En lugar de reiniciar su computadora cada vez que realiza cambios o transfiere archivos desde su computadora de producción a la máquina de prueba, puede usar una aplicación para ejecutar el sistema operativo en una máquina virtual mientras su sistema operativo actual continúa ejecutándose. Las aplicaciones de VM incluyen VMWare (que también tiene un servidor disponible gratuitamente), código abierto alternativo, Bochs, Microsoft Virtual PC (no compatible con Linux) y XVM VirtualBox.
Lanzar una versión de lanzamiento. Esto permitirá a los usuarios informarle sobre posibles fallas en su sistema operativo.
-
El sistema operativo también debe ser fácil de usar, así que asegúrese de agregar funciones útiles que se conviertan en una parte integral de su diseño.
- Una vez que se complete el desarrollo, considere si desea que el código esté disponible públicamente o sea privado.
- Asegúrese de que las funciones de seguridad sean su máxima prioridad si desea que su sistema sea viable.
- No inicie un proyecto de desarrollo de sistema operativo con el objetivo de aprender a programar. Si no conoce C, C++, Pascal o cualquiera de los otros lenguajes y funciones relevantes, incluidos tipos de puntero, operaciones de bits de bajo nivel, conmutación de bits, ensamblador en línea, etc., entonces aún no está listo. para crear sistema operativo.
- Explora portales como OSDev y OSDever para ayudarte a mejorar tu propio sistema operativo. Tenga especialmente en cuenta que, para la mayoría de los problemas, la comunidad OSDev.org prefiere que usted mismo consulte el contenido del sitio en lugar de unirse a un foro. Sin embargo, si decide unirse a las filas de los miembros del foro, deben existir ciertos requisitos previos para ello. Debe tener un conocimiento profundo de C o C++ y del lenguaje ensamblador x86. También debe comprender conceptos generales y complejos de programación, como listas vinculadas, colas, etc. La comunidad OSDev establece explícitamente en sus reglas que nadie cuidará a los nuevos programadores. Si estás intentando desarrollar un sistema operativo, no hace falta decir que eres un dios de la programación. También debe leer el manual del procesador sobre la arquitectura del procesador que seleccione; por ejemplo x86 (Intel), ARM, MIPS, PPC, etc. Un libro de referencia de este tipo sobre la estructura del procesador se puede encontrar fácilmente buscando en Google (“Manuales Intel”, “Manuales ARM”, etc.). No se una al foro OSDev.org para hacer preguntas obvias. Esto simplemente dará como resultado respuestas como "Lea el maldito manual". Primero, deberías intentar leer Wikipedia, los manuales de las distintas herramientas que vas a utilizar.
- Verifique posibles puntos muertos y otros errores. Las lagunas, los callejones sin salida y otros problemas pueden afectar el diseño de su sistema operativo.
- Si desea un método más sencillo, piense en distribuciones de Linux como Fedora Revisor, Custom Nimble X, Puppy Remaster, PCLinuxOS mklivecd o SUSE Studio y SUSE KIWI. Sin embargo, el sistema operativo que se está creando pertenece a la empresa que introdujo por primera vez este servicio (aunque usted tiene derechos para distribuirlo, cambiarlo y ejecutarlo libremente como desee según la GPL).
- Una buena solución sería crear una partición completamente nueva para el sistema operativo que se está desarrollando.
Advertencias
- Copiar descuidadamente el sistema operativo en el disco duro puede dañarlo por completo. Ten cuidado
- No tendrás un sistema completamente terminado en dos semanas. Comience con un sistema operativo de arranque y luego pase a cosas más interesantes.
- Si hace algo imprudente, como escribir bytes aleatorios en puertos de E/S aleatorios, destruirá su sistema operativo y (en teoría) podría quemar su hardware.
- No espere que sea fácil construir un sistema operativo de calidad. Hay muchas interdependencias complejas. Por ejemplo, para que el sistema operativo pueda manejar múltiples procesadores, su administrador de memoria debe tener mecanismos de "bloqueo" para evitar que procesadores adicionales accedan al mismo recurso al mismo tiempo. Los "bloques" utilizados implican un programador para garantizar que sólo un procesador acceda a un recurso crítico en un momento dado, mientras que todos los demás están inactivos. Sin embargo, el funcionamiento del planificador depende de la presencia del administrador de memoria. Este es un ejemplo de dependencia de punto muerto. No existe una forma estándar de resolver problemas como este; Se espera que cada creador de sistemas operativos tenga la habilidad suficiente para crear su propia solución.
Primero, aprenda programación. Se requieren conocimientos de ensamblador; También es muy recomendable tener conocimientos de otros lenguajes de programación adicionales de nivel inferior, como C.
Decide en qué dispositivo quieres cargar el sistema operativo. Podría ser un CD, DVD, dispositivo de memoria flash, disco duro u otra computadora.
Decide cómo quieres que se vea tu sistema operativo.¿Debería ser un sistema operativo completo con una interfaz gráfica de usuario (GUI) o tal vez algo más minimalista? Necesita saber en qué dirección ir antes de comenzar el proceso.
Compruebe qué plataforma de procesador admitirá su sistema operativo. AI-32 y x86_64 son las dos versiones más comunes para computadoras personales, por lo que pueden considerarse la mejor opción.
Decida si prefiere hacerlo todo usted mismo desde cero o si existen núcleos a partir de los cuales le gustaría construir el sistema.
Linux desde cero es un proyecto para aquellos que quieren, por ejemplo, crear su propia distribución de Linux.
Elija si va a utilizar su propio gestor de arranque o el Grand Unified Bootloader (GRUB) prediseñado. Por supuesto, es muy posible desarrollar un sistema operativo en un lenguaje como Pascal o BASIC, pero es preferible escribirlo en C o lenguaje ensamblador. El lenguaje ensamblador es absolutamente necesario, ya que algunas partes importantes del sistema operativo requieren conocimientos de este lenguaje en particular. C++, por otro lado, contiene las palabras clave necesarias para ejecutar versión completa SO.
rTYCHEFUFCHHA CHUEI UCHPYI YUIFBFEMEK!
rTEDSHDHEYE CHSHCHRKHULY NPZMY VSCHFSH OEULPMSHLP ЪBRХФBOOSCHNY. oBUBMSHOBS ЪБЗТХЛБ, ensamblador, BIOS. UEZPDOS NSCH OBLPOEG RETEYIDYN L VPMEE YOFETEUOPK Y RPOSFOPK YUBUFY - NSCH OBUYOBEN RYUBFSH SDTP. th RYUBFSH NSCH EZP VKhDEN ACERCA DEL SESSCHLE CHCHUPLPZP HTPCHOS y.
h OBYUBMSHOSHCHK ЪBZTHYUYL PUFBMPUSH CHOEUFY CHUEZP RBTH DPRPMOEOYK Y PO VHDEF RPMOPUFSHA ZPFPCH ZTHYFSH MAVSHCHE 32-VYFOSHCHE SDTB.
PRTEDEMEOYE PVAENB PRETBFYCHOPK RBNSFY
lPOYUOP, NPTsOP RPDUDUYFBFSH PVIAYN RBNSFY CHTHYUOHA CH SDTE - RETEVYTBFSH BDTEUB PF 0x100000 Y RSHCHFBFSHUS ЪBRYUBFSH FKhDB OBYUEOYE PFMYYUOPE PF OHMIOS Y 0xFF. eUMY RTY YUFEOYY NSCH RPMKHYUBEN RPMHYUOOPE OBYUEOYE, FP CHUЈ IPTPYP, YOBYUE RBNSFSH LPOYUYMBUSH - ЪBRPNYOBEN BDTEU RPUMEDOEZP HDBYUOPZP YUFEOYS, LFP Y VHDEF PVIENNPN PRETBFYCHOPK RBNSFY. pDOBLP FBLPC URPUPV YNEEF DCHB OEDPUFBFLB:
1) EZP UMEDHEF YURPMSHЪPCHBFSH DP CHLMAYUEOYS UFTBOYUOPK BDTEUBGYY, YUFPVSH YNEFSH DPUFHR LP CHUEK ZHYYUEULPK RBNSFY, MYVP KHUFTBYCHBFSH ЪBRYUSH YUETE "PLOP CHTENEO" OPK UFTBOIGSHCH. mYYOSS FTBFB CHTENEY, RTY KHUMPCHYY, UFP FEUFYTPCHBOYE RBNSFY BIOS Y FBL CHSHRPMOSEF RTY OBYUBMSHOPK YOYGYBMYBGYY, B NSCH DEMBEN DCHPKOHA TBVPFH.
2). l FPNH CE BIOS RYYEF CH UBNHA PVSHYUOKHA RBNSFSH FBVMYGSH ACPI, LPFPTSHCHE RTYZPDSFUS PRETBGYPOOPK UYUFEN Y OE UPYF YI ЪBFYTBFSH DP RTPYUFEOOS.
yЪ LFPPZP UMEDHEF, YuFP MHYUYE URPTPUYFSH RTP PVYAEN PRETBFYCHOPK RBNSFY X BIOS, VMBZP ON RTEDPUFBCHMSEF CHUE OEPVIPDYNSCHE ZHKHOLGYY.
yUFPTYYUEULY RETCHPK ZHKHOLGYEK PRTEDEMEOYS PVYANB PRETBFYCHOPK RBNSFY VSHMP RTETSCHCHBOIE 0x12. POP OE RTOYNBEF OILBLYI CHIPDOSCHI RBTBNEFTPCH, CH ACERCA DE CHSHCHIPDE CH TEZYUFTE AX UPDETSYFUS TBNET VBPPChPK RBNSFY CH LYMPVBKFBI. vББПЧБС RBNSFSH - FE UBNSHCHE 640 lv DPUFHROSCHE CH TEBMSHOPN TETSINE. UEKYUBU CHSC HCE OE UNPTSEFE OBKFY LPNRSHAFET, ZDE VSH VSHMP NEOEE 640 lev RBNSFY, OP NBMP MY. yURPMSHЪPCHBFSH EЈ OBN UNSHUMB OEF - EUMY RTPGEUUPT RPDDETSYCHBEF ЪBEEYEЈOOOSCHK TETSYN, FP CHTSD MY KH OEZP VKhDEF NEOSHYE OEULPMSHLYI NEZBVBKF RBNSFY.
pVYaЈNSCH RBNSFY TPUMY Y 640 lv UFBMP NBMP. fPZDB RPSCHYMBUSH OPCHBS ZHKHOLGYS - RTETSCHCHBOYE 0x15 AH=0x88. pOB CHP'CHTBEBEF CH AX TBNET TBUYYTEOOPK RBNSFY (ESTUDIANTES 1 nv) CH LYMPVBKFBI CH AX. bFB ZHOLGYS OE NPTSEF CHPCHTBEBFSH OBYUOYS VPMSHYE 15 de noviembre (15 + 1 YFPZP 16 de noviembre).
lPZDB Y 16 nv UVBMP OEDPUFBFPYuOP RPSCHYMBUSH OPCHBS ZHOLGYS - RTETSCHCHBOIE 0x15, AX=0xE801. POB CHPCHTBEBEF TEKHMSHFBFSCH BC CH 4 TEZYUFTBI:
AX - TBNET TBUYYTEOOOPK RBNSFY DP 16 nv Ch LYMPVBKFBI
BX - TBNET TBUYYTEOOOPK RBNSFY UCHETI 16 nv L VMPLBI RP 64 lv
CX - TBNET ULPOZHYZHTYTPCHBOOPC TBUYYTEOOPK RBNSFY DP 16 nv Ch LYMPVBKFBI
DX - TBNET ULPOZHYZHTYTPCHBOOPC TBUYYTEOOPK RBNSFY UCHETI 16 nv h VMPLBI RP 64 lv
YuFP FBLPE "ULPOZHYZHTYTPCHBOOBS" RBNSFSH RTPYCHPDYFEMY BIOS UHDS RP CHUENKH OE DPZPCHPTYMYUSH, RPFPNKH OBDP RTPUFP, EUMY CH AX Y BX OHMY, VTBFSH OBYUE OYE CX Y DX.
oP Y LFPZP PLBBBMPUSH NBMP. CHEDSH CHUE RETEYUUMEOOSCH CHE ZHKHOLGYY YNEAF PZTBOYUEOOYE PVAENB RBNSFY CH 4 estrellas, L FPNH TSE OE KHYYFSHCHBAF FP, YuFP RBNSFSH NPTsEF VSCFS OE OERTETCHCHOSCHN VMPLPN. rPFPNH CH OPCHSHHI BIOS RPSCHYMBUSH EEЈ PDOB ZHOLGYS - RTETSCHCHBOIE 0x15, AX=0xE820. pOB CHPCHTBEBEF OE RTPUFP YUYUMP, B LBTFH RBNSFY. CHIPDOSHCHE RBTBNEFTSCH:
EAX=0xE820
EDX=0x534D4150 ("SMAP")
EBX - UNEEEEOYE PF OYUBMB LBTFSH RBNSFY (DMS OYUBMB 0)
ECX - TBNET VKHZHETB (LBL RTBCHYMP 24 VBKFB - TBNET PDOPZP BMENEOFB)
ES:DI - BDTEU VKHZHETB, LKhDB OBDP ЪBRYUBFSH PYUETEDOPK BMENEOF
CHPIPDOSHE RBTBNEFTSCH:
EAX=0x534D4150 ("SMAP")
EBX - OPChPE UNEEOOYE DMS UMEDHAEEZP CHSHCHJPCHB ZHKHOLGYY. eUMY 0, FP CHUS LBTFB RBNSFY RTPYUIFBOB
ECX - LPMYUEUFChP TEBMSHOP CHPCHTBEYOOOSCHI VBKF (20 YMY 24 VBKFB)
h KHLBBOOPN VKHZHETE UPDETSYFUS PYUETEDOPK BMENEOF LBTFSH RBNSFY.
LBTSDSCHK BMENEOF LBTFSH RBNSFY YNEEF UMEDHAEHA UFTHLFHTH (OBRYYKH CH UYOFBLUIUE UI, RPFPNKH YuFP TBVPT DBOOSHI NSCH VKHDEN DEMBFSH HTSE CH SDTE):
Estructura ( base larga sin signo; //vБПЧШЧК ЖЪЪУУУУЛК БДТУ ТЭЗІПOB longitud larga sin signo; //тБЪNET TEZYPOB CH VBKFBI tipo largo sin signo; // fYR TEZYPOB acpi_attrs largos sin signo; //tBUYYTEOOSH BFTY VHFSCH ACPI);
rPUMEDOYK BMENEOF UFTKHLFKhTSCH OE PVSBFEMEO. eeЈ CH PDOPN YUFPYUOYLE CHYDEM, UFP RETED ЪBRTPUPN BMENEOFB UFPYF RPNEUFYFSH FHDB EDYOYULH. lPOYUOP, UEKYUBU NSCHOE RPDDETSYCHBEN ACPI, OP MHYUYE ЪBTBOEE RPBBVPFYFUS P FPN, YUFPVSH RPMKHYUYFSH LBL NPTsOP VPMSHYE DBOOSCHI. h PFMYYUYY PF RBTBNEFTPC RBNSFY, CHU PUFBMSHOP NPTsOP MEZLP KHOBFSH YY ЪBEEYEЈOOOPZP TETSINB OBRTSNHA, VE BIOS.
TEZYPOSH RBNSFY, PRYUSCHCHBENSHCH LBTFPC, NPZHF VShchFSH OUEULPMSHLYI FYRPCH:
1 - pVSHYUOBS RBNSFSH. nPTsEF VShchFSH UCHPVPDOP YURPMSHЪPCHBOB pu DMS UCHPYI GEMEK. rPLB NSCH FPMSHLP L OEK Y VKhDEN PVTBEBFSHUS, B CHUЈ PUFBMSHOP RTPRKHULBFSH.
2 - ъБТЭЧТПЧБОП (OBRTYNET, LPD BIOS). bFB RBNSFSH NPTSEF VSCHFSH LBL ZHYYYUUEULY OEDPUFHROB DMS ЪBRYUY, FBL Y RTPUFP ЪBRYUSH FHDB OETSEMBFEMSHOB. fBLHA RBNSFSH MHYUYE OE FTPZBFSH.
3 - dPUFHROP RPUME RTPYUFEOOIS FBVMYG ACPI. CHETPSFOP, YNEOOP CH FYI VMPLBI LFY FBVMYGSH Y ITBOSFUS. rPLB DTBKCHET ACPI OE RTPYUIFBEF FBVMYGSHCH, bFH RBNSFSH MHYUYE OE FTPZBFSH. rPFPN NPTsOP YURPMSHЪPCHBFSH FBL CE, LBL Y RBNSFSH FYRB 1.
4 - bFH RBNSFSH UMEDHEF UPITBOSFSH NETSDH NVS UEUUYSNY. fBLHA RBNSFSH NSCH FTPZBFSH OE VKhDEN, RPLB OE KHOBEN, YuFP FBLPE NVS UEUUYY:-)
OE CHUE BIOS NPZHF RPDDETSYCHBFSH UFKH ZHKHOLGYA. eUMY LBLBS-FP ZHOLGYS OE RPDDETSYCHBEFUS, FP RTY CHSHCHIPDE YЪ OEЈ KHUFBOPCHMEO ZHMBZ RETERPMOOYS Y UMEDHEF PVTBEBFSHUS L VPMEE UFBTPC. nsch VKhDEN YURPMSHЪPCHBFSH ZHTNBF LBTFSH RBNSFY ZHKHOLGYY 0xE820. еUMY UBNH YFKH ZHOLGYA CHSHCHBFSH OE RPMKHYUMPUSH - RPMKHYUBFSH PVYAIN RBNSFY PVSCHYUOSCHNY UTEDUFCHBNY Y UPJDBCHBFSH UCPA UPVUFCHEOKHA LBTFH RBNSFY Y PDOPZP FB. rPULPMSHLH PRTEDEMEOYE PVYaЈNB RBNSFY ЪBDББУБ ОХЦОБС ЪДС ЪБРХУЛБ 32-VYFOPZP ЪДС ЪБРХУЛБ 64-VYFOPZP SDTB, MHYUYE PZhPTNYFSH E Ј CH CHYDE RPDRTPZTBNNNSCH. lBTFKH RBNSFY TBNEUFYN RP BDTEUKH 0x7000. OE DKHNBA, YuFP POB NPTsEF VShchFSH VPMSHYE RBTSH LYMPVBKF. rPUMEDOYK BMENEOF CHTHYUOKHA UDEMBEN FYRB 0 - FBLPZP FYRB OE CHPTBEBEF BIOS Y LFP Y VHDEF RTYOBLPN LPOGB.
; rPMHYUEOYE LBTFSH RBNSFY get_memory_map: mov di, 0x7000 xor ebx, ebx @: mov eax, 0xE820 mov edx, 0x534D4150 mov ecx, 24 mov dword, 1 int 0x15 jc @f add di, 24 test ebx, ebx jnz @b @: cmp di, 0x7000 ja .ok mov dword, 0x100000 mov dword, 0 mov dword, 0 mov dword, 1 mov dword, 0 mov ax, 0xE801 int 0x15 jnc @f mov ah, 0x88 int 0x15 jc .ok mov cx, ax xor dx , dx @: prueba cx, cx jz @f mov ax, cx mov bx, dx @: movzx eax, ax movzx ebx, bx mov ecx, 1024 mul ecx push eax mov eax, ebx mov ecx, 65536 mul ecx pop edx agregar eax, edx mov, eax add di, 24 jmp .ok .ok: xor ax, ax mov cx, 24/2 rep stosw ret
OH CHPF Y ZPFPCH OBY OBYUBMSHOSHCHK ЪБЗТХЪУYЛ VHI 32-VYFOSHI SDEt. h ЪBLMAYUEOYE RTYCHPTSKH EZP RPMOSHCHK LPD Y NSCH RETEKDEN L SDTH.
; oYUBMSHOSHCHK ЪБЗТХЪУYЛ SDTB DMS BTIIFELFHTSCH formato x86 Binario como "bin" org 0x7C00 jmp boot; ¿ListFS align 4 fs_magic dd? fs_version dd? fs_flagsdd? fs_base dq? fs_size dq? fs_map_base dq? fs_map_size dq? fs_first_file dq? fs_uid dq? fs_block_size dd? ; ъБЗПМПЧПЛ ЖБКМБ virtual en 0x800 f_info: f_name rb 256 f_next dq ? ..",0 ok_msg db "OK",13,10,0 config_file_name db "boot.cfg",0 start16_msg db "Iniciando kernel de 16 bits...",13,10,0 start32_msg db "Iniciando kernel de 32 bits. ..",13,10,0 etiqueta module_list en 0x6000 etiqueta memoria_map en 0x7000; @b @: mov byte, 0 mov ax, si pop si ret; push si mov si, load_msg_suffix call write_str pop si push si bp mov dx, word mov ax, word @: push ax llama split_file_name mov bp, ax pop ax llama find_file test byte, 1 jz @f mov si, bp mov dx, word mov ax, word jmp @b @: llama a load_file_data mov si, ok_msg llama write_str pop bp si ret; 1 int 0x15 jc @f add di, 24 test ebx, ebx jnz @b @: cmp di, 0x7000 ja .ok mov dword, 0x100000 mov dword, 0 mov dword, 0 mov dword, 1 mov dword, 0 mov ax, 0xE801 int 0x15 jnc @f mov ah, 0x88 int 0x15 jc .ok mov cx, ax xor dx, dx @: prueba cx, cx jz @f mov ax, cx mov bx, dx @: movzx eax, ax movzx ebx, bx mov ecx, 1024 mul ecx push eax mov eax, ebx mov ecx, 65536 mul ecx pop edx agregar eax, edx mov, eax agregar di, 24 jmp .ok .ok: xor ax, ax mov cx , 24/2 repeticiones stosw ret ; rTPDPMTSEOYE OBUBMSHOPZP ЪБЗТХЪУЛБ boot2: ; ъБЗТХЪН ЛПОжІЗХТБГИПООСЧК ЖБКМ ЪБЗТХЪУЛБ mov si, config_file_name mov bx, 0x1000/16 call load_file; CHSPMOYN ЪБЗТХПУОШК ULTYRF mov bx, 0x9000 / 16 mov bp, module_list mov dx, 0x1000 .parse_line: mov si, dx .parse_char: lodsb test al, al jz .config_end cmp al, 10 je .run_command cmp al, 13 je .run_command jmp .parse_char .run_command: mov byte, 0 xchg dx, si cmp byte, 0 je .parse_line; rHUFBS UFTPLB cmp byte, "#" es .parse_line; lPNNEOFBTYK cmp byte, "L" es .load_file; ъБЗТХЛБ ЖБКМБ cmp byte, "S" je .start; ъBRHUL SDTB; oEYCHEUFOBS LPNBODB mov al, mov [.cmd], al error de llamada db "Comando de script de arranque desconocido "" .cmd db ? db ""!",13,10,0 .config_end: ; rTY RTBCHYMSHOPN LPOZHYZHTBGYPOOPN ZHBKME NSCH OE DPMTSOSCH UADB RPRBUFSH; reinicio de jmp; ъБЗТХЛБ ЖБКМБ.load_file: push dx inc si call load_file push ax mov cx, 512 mul cx mov palabra, ax mov palabra, dx mov palabra, 0 mov palabra, 0 mov ax, bx mov cx, 16 mul cx mov palabra, ax mov palabra, dx mov palabra, 0 mov palabra, 0 pop ax shr ax, 9 - 4 agregar bx, ax agregar bp, 16 pop dx jmp .parse_line; ъBRHUL SDTB.start: ; rTPCHETYN, YuFP ЪBZTHTSEO IPFS VSC PDIO ZhBKM cmp bx, 0x9000 / 16 ja @f error de llamada db "NO HAY KERNEL CARGADO", 13,10,0 @: ; ъBRPMOSEN RPUMEDOYK BMENEOF URYULB ZHBKMPCH xor ax, ax mov cx, 16 mov di, pb rep stosw ; RETEIPDYN L RTPGEDHTE YOYGYBMYBGYY SDTB DMS OKHTSOPK TBTSDOPUFY inc si palabra cmp, "16" je .start16 palabra cmp, "32" je .start32 ;palabra cmp, "64" ;je .start64 ; oEYCHEUFOBS TSTSSDOPUFSH SDTB error de llamada db "Argumento de comando de inicio no válido",13,10,0; ъBRHUL 16-TBTSDOPZP SDTB.start16: mov si, start16_msg mov bx, module_list mov dl, jmp 0x9000; ъBRHUL 32-TBTSDOPZP SDTB.start32: ; CHCHCHPDYN HCHEDPNMEOYE P ЪBRHULE 32-VYFOPZP SDTB mov si, start32_msg llamada write_str; rTPCHETYN, YuFP RTPGEUUPT OE IHTSE i386 mov ax, 0x7202 push ax popf pushf pop bx cmp ax, bx je @f error de llamada db "Se requiere i386 o mejor", 13,10,0 @: ; rPMKHYUN LBTFH RBNSFY llama a get_memory_map; pYYUFYN FBVMYGSH UFTBOYG xor ax, ax mov cx, 3 * 4096/2 mov di, 0x1000 rep stosw; ъBRPMOIN LBFBMPZ UFTBOIG palabra mov, 0x2000 + 111b palabra mov, 0x3000 + 111b; ъBRPMOIN RETCHHA FBVMYGH UFTBOYG mov eax, 11b mov cx, 0x100000/4096 mov di, 0x2000 @: stosd add eax, 0x1000 loop @b; ъBRPMOYN RPUMEDOAA FBVMYGH UFTBOYG mov di, 0x3000 mov eax, dword o eax, 11b mov ecx, dword shr ecx, 12 @: stosd add eax, 0x1000 loop @b mov word, 0x4000 + 11b; Palabra de movimiento de pila del kernel, 0x3000 + 11b; Tabla de páginas del núcleo; ЊБЗТХЪН ЪБУЕОЕЧ CR3 mov eax, 0x1000 mov cr3, eax ; ъБЗТХХЪН ЪБУУЭОЭ Ч GDTR lgdt; ъBRTEFYN RTETSCHCHBOYS cli; RETEKDEN CH ЪBEEYEЈOOOSCHK TETSYN mov eax, cr0 o eax, 0x80000001 mov cr0, eax ; RETEKDEN ACERCA DE 32-VYFOSHCHK LPD jmp 8:start32; fBVMYGB DEULTYRFPTPCH UEZNEOFPPCH DMS 32-VYFOPZP SDTB alinear 16 gdt32: dq 0 ; NULO - 0 dq 0x00CF9A000000FFFF; CÓDIGO - 8 dq 0x00CF92000000FFFF ; DATOS - 16 gdtr32: dw $ - gdt32 - 1 dd gdt32 ; 32-VYFOSHCHK LPD uso32 inicio32: ; oBUFTPYN UEZNEOFOSH TEZYUFTSHY UFEL mov eax, 16 mov ds, ax mov es, ax mov fs, ax mov gs, ax mov ss, ax mov esp, 0xFFFFDFFC ; rPNEUFYN CH DL OPNET ЪБЗТХЪПУОПЗП ДУЛБ mov dl, ; rPNEUFYN CH EBX BDTEU URYULB ЪБЗТХЦеOOШ itsХБКМПЧ mov ebx, module_list ; rPNNEUFYN CH ESI BDTEU LBTFSH RBNSFY mov esi, mapa_memoria; RETEIDYN ACERCA DE SDTP jmp 0xFFC00000
RETCHPE SDTP
sDTP RPLB KH OBU VKhDEF UPUFPSFSH YJ DCHHI ZHBKMPCH - startup.asm Y main.c. startup.asm OHTSEO DMS FPZP, YuFPVSH VShchFSH KHCHETEOOSCHNY, YuFP KHRTBCHMEOYE RPRBDEF ACERCA DE ZHKHOLGYA kernel_main. CHEDSH POB NPTsEF VSHFSH OE CH OBYUBME ZHBKMB, B UPDETSYNPE startup.o NSCH RPMOPUFSHA LPOFTPMYTHEN Y EUMY HLBTSEN EZP RETCHSHCHN MYOLETKH, FP VHDEN KHRTBCHMSFSH Y RETCHSHNY VBKFBNY DCHPYUOPZZHBKMB .
Formato ELF público _start extrn kernel_main sección ".text" ejecutable _start: movzx edx, dl push edx push esi push ebx lgdt call kernel_main @: ;cli ;hlt jmp @b sección ".data" grabable gdt: dq 0 dq 0x00CF9A000000FFFF dq 0x00CF92000000 FFFF gdtr: dw $ - gdt dd gdt
OH CHPF Y RPUMEDOYK ACERCA DE LPD ACERCA DE YUYUFPN Ensamblador :-). POR CHSHCHRPMOSEF RTPUFEKYKHA ЪBDББУХ - ХМПЦИФШ Ш UFEL FTY BTZKHNEOFB DMS ZHKHOLGYY kernel_main Y RETEDBFSH ACERCA DE OEE HRTBCHMEOYE. rPUME CHPCHTBFB YЪ OEЈ SDTP KHIPDYF CH VEULPOYUOSCHK GYLM. rP UPZMBYEOYA CHSHCHJPCHB ZHKHOLGYK ui RBTBNEFTSCH UMEDHEF RYIBFSH CH UFEL CH PVTBBPN RPTSDLE. fBLCE LFPF LPD YOIGYBMYBGYY ЪBZTHTSBEF OPCHPE OBYUEOYE CH GDTR - FERTSH FBVMYGB DEULTYRFPTPCH UEZNEOFPCH OBIPDIFUS CH RTPUFTBOUFCHE SDTB Y DBCE EUMY NSCH PFNPO FYTHEN RETCHSCHK NEZBVBKF OE RTPYJPKDEF OILBLYI PYYVPL.
b FERTSH UBNPE CHLHUOPE - RTPUFEKEEE SDTP ACERCA DE LA CONFIGURACIÓN CHCHUPLPZP HTPCHOS:
Estructura Typedef (base larga y larga sin firmar; tamaño largo y largo sin firmar;) BootModuleInfo; void kernel_main(char boot_disk_id, void *memory_map, BootModuleInfo *boot_module_list) ( char *screen_buffer = (void*)0xB8000; char *msg = "¡Hola mundo!"; unsigned int i = 24 * 80; while (*msg) ( screen_buffer = *mensaje++;
bFP SDTP OE DEMBEF OYUEZP PUPVEOOOPZP - RTPUFP CHCHCHPDYF UFTPLH "¡Hola mundo!" ACERCA DE RPUMEDOOAA UFTPYULH FELUFPCHPZP LTBOB. uFTHHLFKHTB PRYUBOOBS CH OBYUBME VKhDEF OHTSOB DMS DPUFKHRB L URYULKH ЪBZTHTSEOOSCHI NPDHMEK.
chBTsOP RPNOYFSH, YuFP OILBLPK UFBODBTFOPK VYVMYPFELY KH OBU OEF - OBN DPUFHROSCH FPMSHLP FE ZHKHOLGYY, LPFPTSHCHE NSCH UDEMBEN UBNY. CHUE printf, strcpy, memcpy Y F. R. RTYDFUS TEBMYЪPCHCHCHBFSH UBNPUFPSFEMSHOP, OE RSCHFBKFEUSH PVTBFYFSHUS L OYN. h UMEDHAEEN CHSHCHRKHULE NSCH ЪБКНЈНУС UPЪDBOYEN OBEZP UPVUFCHEOOPZP TsKhFLP KhTEЪBOOPZP BOBMMPZB libc, YUFPVSH RTPZTBNNNYTPCHBFSH VSHMP KhDPVOEE. FHF OBUYOBEFUS UBNBS YOFETEUOBS YUBUFSH, B RTYOSFSHCHE TYEOYS PE NOPZPN RPCHMYSAF ACERCA DE CHUA UFTHLFHTH UYUFENSCH.
uVPTLB SDTB
yURPMOSENSCH ZHBKMSCH UPVYTBAFUS CH DCHB LFBRB - LPNRYMSGYS, B RPFPN MYOLPCHLB. ACERCA DE RETCHPN LFBR LPNRYMSFPT RTEPVTBHEF YUIPDOSHCHK LPD CH LPNBODSCH RTPGEUUPTB Y UPITBOSEF CHUY LFP CH PVYAELFOSHCHK ZHBKM. LBTSDSCHK NPDHMSH UYUFENSCH UPITBOSEFUS CH PFDEMSHOPN ZHBKME. h LFPN ZHBKME FBL CE UPDETSYFUS YOZHPTNBGYS P ZHKHOLGYSI, PRYUBOOSCHI CH NPDHMY, RPFPNH YЪ PDOPZP ZHBKMB NPTsOP UCHPVPDOP CHSCCHCHBFSH ZHKHOLGYA YЪ DTHZPZP. CHEUSH LPD CH PVAELFOSCHI ZHBKMBI OE RTYCHSBO L LPOLTEFOSHCHN BDTEUBN. ACERCA DE CHFPTPN LFBR MYOLET UPVYTBEF CHUE PVAELFOSCH ZHBKMSCH CH PDYO VYOBTOSHCHK. rTY LFPN LPD RTYCHSCHCHBEFUS L LPOLTEFOSHN BDTEUBN (EUMY, LPOYUOP, NSC OE UPVYTBEN DIOBNYUEULY ЪBZTHTSBENKHA VYVMYPFELH), CHNEUFP UUSCHMPL O ZHKHOLGYY RPDUFBCHMSAFUS O HTSOSHE BDTEUB. OBN OHTsOP RPMKHYUFSH ACERCA DE CHSHCHIPDE PUPVSHCHK DCHPYUOSCHK ZhBKM. ьFP RTPUFP LPD Y DBOOSCH, VEЪ LBLYI-MYVP ЪБЗПМЧЛПЧ (ФП EUФШ ЛФП OE PE І OE ELF). h LBYUEUFCHE VBPCHPZP BDTEUB YURPMSH'HEFUS BDTEU 0xFFC00000. DMS HRTPEEOYS LFPPZP NSCH PRYYEN CHUЈ, YuFP OBN OHTSOP CH UREGYBMSHOPN ZHTNBFE ULTYRFB ld:
OUTPUT_FORMAT("binario") ENTRADA(_start) SECCIONES ( .text 0xFFC00000: ( *(.text) *(.code) *(.rodata*) ) .data ALIGN(0x1000): ( *(.data) ) .bss ALINEAR(0x1000) : ( *(.bss) ) .vacío ALINEAR(0x1000) - 1: ( BYTE(0) ) )
ьFPF ULTYRF ZPCHPTYF, YuFP OBY ZHBKM VKhDEF METSBFSH CH RBNSFY OERTETCHCHOSCHN VMPLPN OBUYOBS U BDTEUB 0xFFC00000. h UBNPN OBYUBME VKhDEF YDFY UELGYS LPDB, RPFPN UELGYS DBOOSCHI de solo lectura, ЪBFEN PVSHYUOSHI DBOOSCHI, RPFPN OEYOYYBMYYYTPCHBOOSCHI. Chue UELGYY CHSHTPCHOOESCH ACERCA DE TBNET UFTBOYGSCH 4 levs (CHDTHZ NSCH RPFPN ЪBIPFYN ЪBEIFYFSH OB HTPCHOE FBVMYGSHCH UFTBOYG LPD PF ЪBRYUY). rPUMEDOEE PRYUBOYE UELGYY.empty OEPVIPDYNP DMS FPZP, YUFPVSH DBTSE OEYOYYYBYMYYPTPCHBOOSCH RETENEOOOSCH ЪBOINBMY NEUFP CH ZHBKME (FBN VHDHF OHMY). CHEDSH OBYUBMSHOSHCHK ЪBZTHYUYL CHSHCHDEMSEF RBNSFSH DMS SDTB THLPCHPDUFCHHSUSH TBNETPN ZHBKMB.
uPVTBFSH CHUY SDTP NPTsOP UMEDHAEYNY LPNBODBNY:
Fasm startup.asm startup.o gcc -c -m32 -ffreestanding -o main.o main.c ld --oformat=binary -melf_i386 -T script.ld -o kernel.bin startup.o main.o
rBTBNEFT GCC -findependiente KHLBSCCHBEF ENKH PFLMAYUYFSH CHUE UFBODBTFOSCH VYVMYPFELY. CHEDSH SING RTYCHSBOSCH L LPOLTEFOPK PRETBGYPOOPK UYUFENE, B NSCH RYYEN OPCHHA.
uVPTLB PVTBB DYULB
pVPKDHUSH VE MYYOYI LPNNEOFBTYECH Y RTPUFP RTYCHEDH MYOHLUPCHSHCHK ULTYRF UVPTLY PVTBBB:
Dd if=bin/boot.bios.bin of=bin/boot_sector.bin bs=512 count=1 dd if=bin/boot.bios.bin of=disk/boot.bin bs=1 skip=512 cp bin/kernel .bin disk/kernel.bin bin/make_listfs of=disk.img bs=512 size=2880 boot=bin/boot_sector.bin src=./disk
según RTEDRPMBZBEF, YuFP CHUE ULPNRYMYTPCHBOOSCH ZHBKMSCH METSBF Ch bin Ch FELKHEEN LBFBMPZE, B EEЈ YNEEFUS LBFBMPZ disco, Ch LPFPTPN METSYF boot.cfg UMEDHAEEZP UPDETSBOYS:
# Cargando kernel Lkernel.bin # Arranque kernel S32 de 32 bits
eUMY CHU CHUY UDEMBMY RTBCHYMSHOP, RPMKHYUEOOOSCHK PVTB NPTsOP ЪBRKHUFYFSH CH BNHMSFPTE YMY DBTSE ACERCA DE TEBMSHOPN TSEMEYY CHSHCH RPMKHYUFE RPDPVOKHA LBTFYOKH:
ъБЗТХЪУйЛ UYFSHCHCHBEF LPOZHYZHTBGYPOOSCHK ZHBKM, ЪБЗТХЦБЭФ SDTP, RETEYPDYF CH ЪBEEYEЈOOOSCHK TETSYN Y RETEDBЈF ENKH KHRTBCHMEOYE. rPMHYUYCH EZP, OBIYE SDTP CHSHCHCHPDYF RPUMEDOAA UFTPLH ACERCA DE BLTBO. bFP MYYSH OBYUBMP DPMZPZP RKhFY, NSCH RETEIPDYN L UBNPK YOFETEUOPK YUBUFY TBTBVPFLY. FERTSH CHSHCHRKHULY VHDHF ZPTBJDP VPMEE RTPUFSHN DMS CHPURTYSFYS, VMBZPDBTS YURPMSH'PCHBOYA SJSHLB CHSHCHUPLPZP HTPCHOS, LPFPTSCHK LBL S OBDEAUSH CHUE Y FBL ЪOBAF. eUMY CHCH OE IPFYFE TBVYTBFSHUS U Ensamblador, NPTSEFE RTPUFP CHOSFSH NPK ZPFPCHSHCHK UBZTHYUYL Y startup.asm Y YNEOSFSH HCE FPMSHLP UPDETSINPE main.c, RPULPMSHLH CHEUSH LPD DP LFPZP OE DYLFHEF TsЈUFLP LBLYE-MYVP RBTBNEFTSH SDTB (LTPNE zhu U LPFPTPK NSCH ЪBZTHTSBENUS) Y RPЪCHPMSEF RPUTPIFSH ACERCA DE UCHPEK VBJE YFP KhZPDOP.
bChFPNBFYBGYS UVPTLY YMY Makefile
ChSCH NPZMY ЪBNEFYFSH, YuFP CHTHYUOKHA OBVYCHBFSH UFPMSHLP LPNBOD DPUFBFPYUOP KhFPNYFEMSHOP. l FPNH CE OE CHUEZDB EUFSH OEPVIPDYNPUFSH RETELPNRYMYTCHBFSH CHUE ZhBKMSCH. OBRTYNET, EUMY startup.asm OE VSHM YЪNEOЈO, NPTsOP OE CHSHCHCHBFSH fasm. UREGYBMSHOP DMS HRTPEEOYS LPNRYMSGY RTYMPTSEOYK VSHMB RTYDKHNBOB HFYMYFB make, LPFPTBS CHIPDIF CH UFBODSBTFOHA RPUFBCHLH GCC Y MinGW.
mAVPK Makefile UFPYF YЪ OBVPTB RTBCHYM U FBLPK UFTHHLFKhTPK:
YNSGEMYYMYZHBKMB: yNSRETCHPZPyUIIDDOPZPzhBKMB yNSchFPTPZPyUIIDDOPZPzhBKMB...lPNBODSCHLPNRYMSGYY
RETCHPE RTBCHYMP, LPFPTPE DPMTSOP VShchFSH CH MAVPN Makefile - GEMAS todas. hacer UNPFTYF ACERCA DE ЪBCHYUYNPUFY GEMY todos Y LPNRYMYTHEF YI, B ЪBFEN CHSHPRMOSEF LPNBODSCH Y LFK GEMY. DMS LBTSDPK DTHZPK GEMY UOBYUBMB UPVYTBAFUS EЈ ЪBCHYUYNPUFY. rTY LFPN YNS GEMY Y YNS ЪBCHYUINPUFEK NPZHF UPCHRBDBFSH U YNEOBNY TEBMSHOSCHI ZHBKMPCH. h FBLPN UMKHYUBE RETEUVPTLB GEMY RTPYЪPKDF FPMSHLP EUMY YUIPDOYLY VSHMY YЪNEOOSHCH.
eEЈ PDOB GEMSH, LPFPTBS YBUFP YURPMSHЪHEFUS CH Makefile - limpio. еЈ ЪБДББУБ ХДБМІФШ CHUE VYOBTOSH ZHBKMSCH, YUFPVSH OBYUBFSH UVPTLH "U YUYUFPZP MYUFB". chPF FBL NPTSEF CHSHCHZMSDEFSH Makefile VHI SDTB:
Todo: startup.o main.o script.ld ld --oformat=binary -melf_i386 -T script.ld -o kernel.bin startup.o main.o startup.o: startup.i386.asm fasm startup.i386.asm startup.o main.o: main.c gcc -c -m32 -ffreestanding -o main.o main.c clean: rm -v *.o kernel.bin
bFPF FELUF OEPVIPDYNP UPITBOIFSH CH ZHBKM U YNEOEN Makefile (VEY TBUYYTEOYS) CH LBFBMPZ U YUIPDOSHNY FELUFBNY SDTB. FERTSH DPUFBFPYUOP CHSHRPMOYFSH LPNBODH make VEЪ RBTBNEFTPCH, OBIPDSUSH CH LFPN LBFBMPZE Y NSCH RPMKHYUN ZHBKM kernel.bin (MYVP UPPVEEOYS PV PYYVLBY, EUMY YUFP-FP RPYMP OE FBL).
b CHPF FBL S UPVYTBA ЪBZTHYUYL:
Todo: boot.bios.bin boot.bios.bin: boot.bios.asm fasm boot.bios.asm boot.bios.bin clean: rm -v boot.bios.bin
El make_listfs:
Todo: compilar compilar: make_listfs.c gcc -o make_listfs make_listfs.c limpiar: rm -f make_listfs make_listfs.exe
OH Y OBLPOEG TBUULBTSH RTP CHSHCHJPCH DTHZYI Makefile YЪ PDOPZP. con DPUFBFPYUOP MEOYCH, YUFPVSH DBTSE ЪBIPDYFSH CH LBFBMPZY U LBTSDSCHN LPNRPEOFPN UYUFENSH, RPFPNH UPЪDBM 1 Makefile, LPFPTSCHK UPVYTBEF UTBH CHUA UYUFENKH. x NEOS EUFSH RBRLB src, CH OEK RPDLBFBMPZY: arranque, kernel, make_listfs. h UBNPC src OBIPDFUS CHPF FBLPC Makefile:
Todo: make -C boot/ make -C kernel/ make -C make_listfs/ clean: make -C boot/ clean make -C kernel/ clean make -C make_listfs clean
FERETSH, OBIPDSUSH LBFBMPZE src S RTPUFP RYYKH make Y RPMHYUBA RPMOPUFSHA UPVTBOOHA UYUFENKH, B EUMY OBRYUBFSH make clean, FP CHUE DCHPYYUOSCH ZHBKMSCH VHDHF KHDBMEOSH Y PUFBOKHFUS FPMSHLP Y WHIPDOYLE.
OH Y CH DPCHETYEOYE RPUMEDOYK ULTYRF, LPFPTSCHK CHSHRPMOSEF RPMOHA LPNRYMSGYA Y UVPTLH CHUEI LPNRPEOFPCH Y PVTBB DYULB. h PDOPN LBFBMPZE U OIN OBDP TBNEUFYFSH src, RHUFPK LBFBMPZ bin Y LBFBMPZ disco U ZHBKMPN boot.cfg.
#!/bin/sh make -C src cp src/boot/boot.bios.bin bin/ cp src/kernel/kernel.bin bin/ cp src/make_listfs/make_listfs bin/ dd if=bin/boot.bios.bin of=bin/boot_sector.bin bs=512 count=1 dd if=bin/boot.bios.bin of=disk/boot.bin bs=1 skip=512 cp bin/kernel.bin disk/kernel.bin bin/make_listfs of=disk.img bs=512 size=2880 boot=bin/boot_sector.bin src=./disk read -p "Presione Enter para continuar..." tonto
u FBLYN OBVPTPN ULTYRFPCH UVPTLB UYUFENB UFBOPCHYFUS RTEDEMSHOP RTPUFPK, PUPVEOOOP EUMY HYUEUFSH, YuFP RPUMEDOYK ULTYRF NPTsOP ЪBRHULBFSH DCHPKOSCHN LMYLPN YJBKMPCHPZP NEOEDCETB. tBMYUOSHE LPNBODSCH CHTPDE dd, cp, rm OE UHEEUFCHHAF RPD Windows, RPFPNH EЈ RPMSHЪPCHBFEMSN RTYZPDYFUS RBBLEF MSYS YMY Cygwin. pDOBLP RTPUFBS UVPTLB CHUEI LPNRPEOFPCH VHDEF TBVPFBFSH DBCE EUMY X CHBU EUFSH FPMSHLP GCC Y fasm (make_listfs MEZLP ULPNRYMYTHEFUS Y ЪBRHUFYFUS CHYDE Windows-RTYMPTSEOYS ).
rTYNEYUBOYE DMS RPMSHЪPCHBFEMEK pu Windows
ld DMS Windows OE UPCHUEN RPMOPGEOOSHCHK - PO OE RPDDETSYCHBEF CHSHCHPD UTBH CH VYOBTOSHCHK ZHBKM, FPMSHLP CH EXE. YURTBCHYFSH LFP NPTsOP UPЪDBCH UOBYUBMB EXE (ld OE PVTBFYF CHOYNBOYE, YuFP VBPCHSHHE BDTEUB UELGYK OECHPNPTSOSCHE DMS CHEODPCHSHHI VYOBTOILPC), B RPFPN ChShchFBEYFSH PFFHDB YUUFSHCHE D MÁS EN RPNPESH objcopy. eUMY CHSH UFPMLOЈFEUSH U FEN, YuFP ld PFLBSCHCHBEFUS UPЪDBCHBFSH ZhBKM kernel.bin, CHPURPMSHKFEUSH CHPF FBLYN Makefile VHI SDTB:
Todos: startup.o main.o script.ld ld -melf_i386 -T script.ld -o kernel.bin startup.o main.o objcopy kernel.bin -O binario startup.o: startup.i386.asm fasm startup.i386 .asm startup.o main.o: main.c gcc -c -m32 -ffreestanding -o main.o main.c clean: rm -v *.o kernel.bin
Utilice OUTPUT_FORMAT("binary") en script.ld. FERETSH Y RPD Windows RPMKHYUFUS UPVTBFSH SDTP UYUFENSCH.
ъБЗТХЛБ УУИУФНШХ ACERCA DE TEBMSHOPK NBYYOE
rPUME FBLYI KHUREIPCH X OELPFPTSCHI NPTSEF CHP'OILOHFSH TSEMBOYE PRTPVPCHBFSH OPCHHA pu ACERCA DE TEBMSHOPN TSEMEYE. lFP OE RTEDUFBCHMSEF RTPVMEN. u RPNPESHA HxD CH Windows PFLTPKFE DYULEFH YMY ZHMEYLH, CHSHVTTBCH CHBTYBOF "pFLTSCHFSH DYUL". rTY PFLTSCHFY ZHMEYLY CHBTsOP PFLTSCHFSH YNEOOOP UBNH ZHMEYLKH, B OE TB'DEM. h DTHZPK CHLMBDLE PFLTPKFE disk.img, CHSHCHDEMYFE EZP UPDETSYNPE RPMOPUFSHA Y ULPRYTHKFE OB DYUL U EZP UBNPZP OBYUBMB. rPUME bfpzp ntsop obtsbfsh "upitboyfsh" y dptsdbfshus plpoyuboys bbryuy. CHUE DBOOSCH ACERCA DE YMY DYULEF RTY LFPN VHDHF HOYUFPTSEOSH, B DMS FPZP, YUFPVSH EЈ YURPMSHЪPCHBFSH UOPCHB RP OBYUOYA, EЈ RTYDFUS ЪBOPChP PFZhPTNBFYTPCHBFSH !
rPMSHЪPCHBFEMY Linux NPZHF RPUFKHRYFSH RTPEE - CHSHRPMOYFSH UREGYBMSHOHA LPNBODH CH FETNYOBME. DMS DYULEFSH:
Dd si=disk.img of=/dev/fd0
DMS ZHMEYLY:
Dd si=disk.img of=/dev/sdX
chNEUFP sdX OBDP RPDUFBCHYFSH OBUFPSEEE YNS KHUFTPKUFCHB (sda, sdb, sdc, sdd Y F. D.). ZMBCHOPE RTY LFPN OE RETERKHFBFSH Y OE ЪBRYUBFSH PVTB ACERCA DE UYUFENOSHCHK DYUL, HOYUFPTSYCH CHUE DBOOSCH. TBHNEEFUS, PVE LPNBODSCH DPMTSOSCH CHSHRPMOSFSHUS PF YNEOY root YMY Y RPNPESH sudo.
rPUME LFPPZP OBDP OBUFTPIFSH CH BIOS ЪБЗТХЛХ У ДУЛЭФШЧ ІМИ ЖМИЛХ (UFBTSHCHE BIOS OE RPDDETSYCHBAF ZHMEYLY) Y OBUMBTSDBFSHUS CHYDPN "Hola mundo".
ъBLMAYUEOOYE
OH CHPF UPVUFCHOOOP Y CHUЈ SOBRE UEZPDOS. nsch OBLPOEG-FP ЪBLPOYUMY RTPZTBNNNYTPCHBOYE ACERCA DE Assembler (IPFS CH U CHU TBCHOP RTYDFUS YOPZDB DEMBFSH BUUENVMETOSCHE CHUFBCHLY DMS TBVPFSCH U PVPTKHDPCBOYEN) Y RETEYMY ACERCA DE L CHCHUPLPZP HTPCHOS. eEЈ PYUEOSH NOPZP RTEDUFPYF UDEMBFSH. NSC NPTSEFE HCE RTPCHPDYFSH TBMYUOSCH LURETYNEOFSH, JNEOSS NPK main.c, FPMSHLP KHYUFYFE, YuFP MAVBS PYYVLB (DPUFHR L OEURTPEGYTPCHBOOPC RBNSFY, DEMEOYE ACERCA DE RTICHED) F L RETEЪBZTHYLE YMY ЪBCHYUBOYA UYUFENSCH (NSCH RPLB PVTBVB FSCHBEN YULMAYUEOYS, RPFPNH RTPGEUUPT OE NPTSEF RTDPDPMTSYFSH TBVPFKH RPUME PYYVLY ) . dP CHUFTEYUY!
MAvsche CHPRPTUSCH CHSC NPTSEFE ЪBDBFSH ACERCA DE NPK BDTEU: [correo electrónico protegido]. y DB, UEKYUBU UBNPE CHTENS DMS TBMYUOSHI YDEK RP LPOGERGYY PU Y RTEDMPTSEOYK.
Guía para crear un kernel para un sistema x86. Parte 1. Sólo el núcleo
Escribamos un kernel simple que se pueda iniciar usando el gestor de arranque GRUB en un sistema x86. Este kernel mostrará un mensaje en la pantalla y esperará.
¿Cómo arranca un sistema x86?
Antes de comenzar a escribir el kernel, comprendamos cómo arranca el sistema y transfiere el control al kernel.
La mayoría de los registros del procesador ya contienen ciertos valores al inicio. El registro que apunta a la dirección de las instrucciones (Instruction Pointer, EIP) almacena la dirección de memoria donde se encuentra la instrucción ejecutada por el procesador. El EIP predeterminado es 0xFFFFFFFF0. Por lo tanto, los procesadores x86 a nivel de hardware comienzan a funcionar en la dirección 0xFFFFFFF0. En realidad, estos son los últimos 16 bytes del espacio de direcciones de 32 bits. Esta dirección se llama vector de reinicio.
Ahora el mapa de memoria del chipset garantiza que 0xFFFFFFF0 pertenezca a una parte específica del BIOS, no a la RAM. En este momento, el BIOS se copia a sí mismo en la RAM para obtener más acceso rápido. La dirección 0xFFFFFFF0 solo contendrá una instrucción para saltar a la dirección en la memoria donde está almacenada una copia del BIOS.
Así es como comienza a ejecutarse el código de la BIOS. El BIOS primero busca un dispositivo desde el que pueda arrancar, en un orden preestablecido. Se busca un número mágico para determinar si el dispositivo es de arranque (los bytes 511 y 512 del primer sector deben ser iguales a 0xAA55).
Cuando el BIOS encuentra un dispositivo de arranque, copia el contenido del primer sector del dispositivo en la RAM, comenzando en la dirección física. 0x7c00; luego va a la dirección y ejecuta el código descargado. Este código se llama gestor de arranque.
El gestor de arranque carga el kernel en una dirección física. 0x100000. Esta dirección se utiliza como dirección inicial en todos los núcleos grandes en sistemas x86.
Todos los procesadores x86 comienzan en un modo simple de 16 bits llamado modo real. El gestor de arranque GRUB cambia el modo a 32 bits modo protegido, estableciendo el bit bajo del registro CR0 en 1 . Por tanto, el kernel se carga en modo protegido de 32 bits.
Tenga en cuenta que en el caso del kernel de Linux, GRUB ve los protocolos de arranque de Linux y arranca el kernel en modo real. El kernel cambia automáticamente al modo protegido.
¿Qué necesitamos?
- computadora x86;
- Linux;
- ld (enlazador GNU);
Establecer el punto de entrada en ensamblador
No importa cuánto quieras limitarte solo a C, tendrás que escribir algo en ensamblador. Escribiremos en él un pequeño archivo que servirá como punto de partida para nuestro kernel. Todo lo que hará será llamar a una función externa escrita en C y detener el flujo del programa.
¿Cómo podemos asegurarnos de que este código sea el punto de partida?
Usaremos un script de vinculación que vincula archivos objeto para crear el archivo ejecutable final. En este script indicaremos explícitamente que queremos cargar datos en la dirección 0x100000.
Aquí está el código ensamblador:
;;kernel.asm bits 32 ;directiva nasm - sección de 32 bits .text inicio global extern kmain ;kmain se define en el archivo c inicio: cli ;el bloque interrumpe mov esp, stack_space ;establece la llamada al puntero de la pila kmain hlt ;detiene la sección de CPU .bss resb 8192; 8 KB para pila stack_space:
La primera instrucción, bits 32, no es una instrucción ensambladora x86. Esta es una directiva para el ensamblador NASM que especifica la generación de código para un procesador que opera en modo de 32 bits. En nuestro caso esto no es necesario, pero en general es útil.
La sección con el código comienza en la segunda línea.
global es otra directiva NASM que hace que los símbolos del código fuente sean globales. De esta manera el enlazador sabe dónde está el símbolo de inicio: nuestro punto de entrada.
kmmain es una función que se definirá en el archivo kernel.c. extern significa que la función está declarada en otro lugar.
Luego viene la función start, que llama a la función kmain y detiene el procesador con la instrucción hlt. Es por eso que deshabilitamos las interrupciones por adelantado usando la instrucción cli.
Idealmente, necesitamos asignar algo de memoria y señalarla con un puntero de pila (especialmente). Sin embargo, parece que GRUB ya lo ha hecho por nosotros. Sin embargo, seguirá asignando algo de espacio en la sección BSS y moverá el puntero de la pila al principio. Usamos la instrucción resb, que reserva el número especificado de bytes. Inmediatamente antes de llamar a kmain, el puntero de la pila (esp) se establece en la ubicación correcta con la instrucción mov.
Núcleo en C
En kernel.asm hicimos una llamada a la función kmain(). Por lo tanto, nuestro código “C” debería comenzar la ejecución con kmai():
/* * kernel.c */ void kmain(void) ( const char *str = "mi primer kernel"; char *vidptr = (char*)0xb8000; //la memoria de video comienza aquí. unsigned int i = 0; unsigned int j = 0; /* este bucle borra la pantalla * hay 25 líneas cada una de 80 columnas cada elemento ocupa 2 bytes */ while(j;< 80 * 25 * 2) { /* blank character */ vidptr[j] = " "; /* attribute-byte - light grey on black screen */ vidptr = 0x07; j = j + 2; } j = 0; /* this loop writes the string to video memory */ while(str[j] != "\0") { /* the character"s ascii */ vidptr[i] = str[j]; /* attribute-byte: give character black bg and light grey fg */ vidptr = 0x07; ++j; i = i + 2; } return; }
Todo lo que nuestro kernel hará será borrar la pantalla y mostrar la línea "mi primer kernel".
Primero creamos un puntero vidptr que apunta a la dirección. 0xb8000. En modo protegido, la “memoria de video” comienza desde esta dirección. Para mostrar texto en pantalla reservamos 25 líneas de 80 caracteres ASCII, empezando por 0xb8000.
Cada carácter no se muestra con los 8 bits habituales, sino con 16. El primer byte almacena el carácter en sí y el segundo, el byte de atributo. Describe el formato del carácter, como su color.
Para mostrar el carácter s verde sobre fondo negro escribiremos este carácter en el primer byte y el valor 0x02 en el segundo. 0 significa fondo negro, 2 significa color de texto verde.
Aquí está la carta de colores:
0 - Negro, 1 - Azul, 2 - Verde, 3 - Cian, 4 - Rojo, 5 - Magenta, 6 - Marrón, 7 - Gris claro, 8 - Gris oscuro, 9 - Azul claro, 10/a - Verde claro, 11/b - Cian claro, 12/c - Rojo claro, 13/d - Magenta claro, 14/e - Marrón claro, 15/f - Blanco.
En nuestro kernel usaremos texto gris claro sobre fondo negro, por lo que nuestro atributo byte tendrá el valor 0x07.
En el primer ciclo, el programa imprime un símbolo en blanco en toda la zona de 80x25. Esto borrará la pantalla. En el siguiente ciclo, los caracteres de la cadena terminada en nulo "mi primer núcleo" con un byte de atributo igual a 0x07 se escriben en la "memoria de video". Esto imprimirá la cadena en la pantalla.
pieza de conexión
Necesitamos ensamblar kernel.asm en un archivo objeto usando NASM; luego use GCC para compilar kernel.c en otro archivo objeto. Luego deben adjuntarse al kernel de arranque ejecutable.
Para hacer esto, usaremos un script vinculante que se pasa a ld como argumento.
/* * link.ld */ OUTPUT_FORMAT(elf32-i386) ENTRY(start) SECCIONES ( . = 0x100000; .text: ( *(.text) ) .data: ( *(.data) ) .bss: ( *( .bss) ) )
primero preguntaremos formato de salida como formato ejecutable y vinculable de 32 bits (ELF). ELF es un formato de archivo binario estándar para sistemas Unix x86. ENTRADA toma un argumento que especifica el nombre del símbolo que es el punto de entrada. SECCIONES- esta es la parte más importante. Define el marcado de nuestro archivo ejecutable. Determinamos cómo se deben conectar los diferentes tramos y dónde colocarlos.
Entre paréntesis después de SECCIONES, el punto (.) muestra el contador de posición, cuyo valor predeterminado es 0x0. Se puede cambiar, que es lo que estamos haciendo.
Veamos la siguiente línea: .text: ( *(.text) ) . El asterisco (*) es un carácter especial que coincide con cualquier nombre de archivo. La expresión *(.text) significa todas las secciones .text de todos los archivos de entrada.
Por lo tanto, el vinculador une todas las secciones de código de los archivos objeto en una sección del archivo ejecutable en la dirección del contador de posición (0x100000). Después de esto, el valor del contador será igual a 0x100000 + el tamaño de la sección resultante.
Lo mismo ocurre con otras secciones.
Grub y arranque múltiple
Ahora todos los archivos están listos para crear el kernel. Pero queda un paso más.
Existe un estándar para cargar núcleos x86 usando un gestor de arranque llamado Especificación de arranque múltiple. GRUB sólo arrancará nuestro kernel si cumple estas especificaciones.
A continuación, el núcleo debería contener un encabezado en sus primeros 8 kilobytes. Además, este encabezado debe contener 3 campos, que son de 4 bytes:
- mágico campo: contiene el número mágico 0x1BADB002 para identificar el núcleo.
- campo banderas: no lo necesitamos, pongámoslo a cero.
- campo suma de control: si lo sumas con los dos anteriores, deberías obtener cero.
Nuestro kernel.asm se verá así:
;;kernel.asm ;directiva nasm - 32 bits bits 32 sección .text ;alineación de especificación de arranque múltiple 4 dd 0x1BADB002 ;magic dd 0x00 ;flags dd - (0x1BADB002 + 0x00) ;suma de comprobación. m+f+c debe ser cero inicio global extern kmain ;kmain se define en el inicio del archivo c: cli ;el bloque interrumpe mov esp, stack_space ;establece la llamada al puntero de la pila kmain hlt ;detiene la sección de la CPU .bss resb 8192 ;8 KB para la pila espacio_pila:
Construyendo el núcleo
Ahora crearemos archivos objeto a partir de kernel.asm y kernel.c y los vincularemos usando nuestro script.
Nasm -f elf32 kernel.asm -o kasm.o
Esta línea ejecutará el ensamblador para crear el archivo objeto kasm.o en formato ELF-32.
Gcc -m32 -c kernel.c -o kc.o
La opción "-c" garantiza que no se produzcan enlaces ocultos después de la compilación.
Ld -m elf_i386 -T link.ld -o kernel kasm.o kc.o
Esto ejecutará el vinculador con nuestro script y creará un ejecutable llamado núcleo.
Configurar grub e iniciar el kernel
GRUB requiere que el nombre del kernel cumpla con el patrón kernel-
Ahora ponlo en el directorio. /bota. Para hacer esto necesitará derechos de superusuario.
En el archivo de configuración de GRUB grub.cfg, agregue lo siguiente:
Título myKernel root (hd0,0) kernel /boot/kernel-701 ro
No olvide eliminar la directiva de menú oculto si está presente.
Reinicie su computadora y verá una lista de kernels, incluido el suyo. Selecciónalo y verás:
¡Este es tu núcleo! Agreguemos un sistema de entrada/salida.
PD
- Para cualquier truco del kernel, es mejor utilizar una máquina virtual.
- Para ejecutar el kernel en comida2 la configuración debería verse así: entrada de menú "kernel 7001" ( set root="hd0,msdos1" multiboot /boot/kernel-7001 ro )
- si desea utilizar el emulador qemu, utilice: qemu-system-i386 -kernel kernel
Leyendo a Habr durante los últimos dos años, he visto solo unos pocos intentos de desarrollar un sistema operativo (específicamente: por parte de los usuarios y (pospuesto indefinidamente) y (no abandonado, pero por ahora parece más una descripción del funcionamiento del modo protegido de procesadores compatibles con x86, que sin duda también es necesario saber para escribir un sistema operativo para x86 y una descripción del sistema terminado (aunque no desde cero, aunque no tiene nada de malo, tal vez incluso lo contrario)); . Por alguna razón, creo que casi todos los programadores de sistemas (y algunos de aplicaciones) han pensado al menos una vez en escribir su propio sistema operativo. En este sentido, 3 sistemas operativos de la gran comunidad de este recurso parece una cantidad ridícula. Al parecer, la mayoría de los que piensan en su propio sistema operativo no van más allá de la idea, una pequeña parte se detiene después de escribir el gestor de arranque, unas pocas escriben partes del kernel y sólo los irremediablemente testarudos crean algo que recuerda vagamente a un sistema operativo. (si se compara con algo como Windows/Linux). Hay muchas razones para esto, pero la principal en mi opinión es que la gente abandona el desarrollo (algunos incluso antes de comenzar) debido a la pequeña cantidad de descripciones del proceso de escritura y depuración de un sistema operativo, lo cual es bastante diferente de lo que sucede. durante el desarrollo del software de la aplicación.
Con esta breve nota me gustaría mostrar que si empiezas correctamente, no hay nada particularmente difícil en desarrollar tu propio sistema operativo. Debajo del corte hay una guía breve y bastante general para escribir un sistema operativo desde cero.
Cómo No hay necesidad empezar
No tome el siguiente texto como una crítica explícita a los artículos o guías de nadie sobre cómo escribir un sistema operativo. Es solo que con demasiada frecuencia en artículos de este tipo con titulares ruidosos se hace hincapié en la implementación de una pieza de trabajo mínima y se presenta como un prototipo del núcleo. De hecho, debería pensar en la estructura del kernel y la interacción de partes del sistema operativo en su conjunto, y considerar ese prototipo como una aplicación estándar de "¡Hola, mundo!" en el mundo del software de aplicaciones. Como ligera justificación de estos comentarios, cabe decir que debajo hay una subsección “¡Hola mundo!”, a la que en este caso se le presta exactamente tanta atención como es necesaria, y nada más.No es necesario escribir un gestor de arranque. A personas inteligentes se les ocurrió la especificación Multiboot, la implementaron y describieron en detalle qué es y cómo usarla. No quiero repetirme, solo diré que funciona, hace la vida más fácil y hay que usarlo. Por cierto, es mejor leer la especificación completa, es pequeña e incluso contiene ejemplos.
No es necesario escribir el sistema operativo completamente en ensamblador. Esto no es tan malo, sino todo lo contrario: los programas rápidos y pequeños siempre serán muy apreciados. Lo que pasa es que, como este lenguaje requiere mucho más esfuerzo para desarrollarse, el uso del ensamblador sólo conducirá a una disminución del entusiasmo y, como resultado, a arrojar las fuentes del sistema operativo en una caja larga.
No es necesario cargar una fuente personalizada en la memoria de video y mostrar nada en ruso. Esto no tiene sentido. Es mucho más fácil y versátil utilizar el inglés, y dejar cambiar la fuente para más adelante, cargándola desde el disco duro a través del controlador del sistema de archivos (al mismo tiempo habrá un incentivo adicional para hacer algo más que empezar).
Preparación
![](https://i1.wp.com/habrastorage.org/getpro/habr/post_images/a55/2a8/17c/a552a817ccabc0ac50015d6a39a51d85.jpg)
Después del programa educativo inicial, es necesario decidir las cuestiones principales:
- Arquitectura de destino: x86 (modo real/protegido/largo), PowerPC, ARM, ...
- Arquitectura kernel/OS: monolito, monolito modular, microkernel, exokernel, varios híbridos
- lenguaje y su compilador: C, C++, ...
- Formato de archivo kernel: elf, a.out, coff, binario, ...
- entorno de desarrollo (sí, esto también juega un papel importante): IDE, vim, emacs, ...
- memoria de video y trabajar con ella: la salida como prueba de funcionamiento es necesaria desde el principio
- HAL (capa de abstracción de hardware): incluso si hay soporte para varias arquitecturas de hardware y no hay planes para separar adecuadamente las partes del nivel más bajo del kernel de la implementación de cosas tan abstractas como procesos, semáforos, etc. no ser superfluo
- Gestión de memoria: física y virtual.
- gestión de ejecución: procesos e hilos, su planificación
- gestión de dispositivos - controladores
- sistemas de archivos virtuales: para proporcionar una interfaz única para el contenido de varios sistemas de archivos
- API (Interfaz de programación de aplicaciones): cómo accederán exactamente las aplicaciones al kernel
- IPC (Comunicación entre procesos): tarde o temprano los procesos tendrán que comunicarse
Herramientas
![](https://i2.wp.com/habrastorage.org/getpro/habr/post_images/371/1cb/385/3711cb385903a76e440144b35af99749.jpg)
- cualquier herramienta estándar es adecuada para el ensamblaje, como make, cmake,... Aquí se pueden usar scripts para el vinculador y utilidades (específicamente escritas) para agregar el encabezado Multiboot. sumas de control o para cualquier otro fin.
- Cuando preparamos una imagen nos referimos a montarla y copiar archivos. En consecuencia, se debe seleccionar el formato del archivo de imagen para que sea compatible tanto con la utilidad de montaje/copia como con la máquina virtual. Naturalmente, nadie prohíbe realizar las acciones a partir de este punto ni como parte final del montaje ni como preparación para el lanzamiento del emulador. Todo depende de los medios específicos y de las opciones elegidas para su uso.
- Iniciar una máquina virtual no es difícil, pero debe recordar desmontar primero la imagen (desmontar en este punto, ya que no tiene sentido realizar esta operación antes de iniciar la máquina virtual). También sería útil tener un script para iniciar el emulador en modo de depuración (si está disponible).
![](https://i2.wp.com/habrastorage.org/getpro/habr/post_images/f9e/ac7/67b/f9eac767b1404ebf6eef95a54293ba95.jpg)
En este paso, debe verificar tantas funciones como sea posible de las herramientas de desarrollo que planea utilizar en el futuro. Por ejemplo, cargar módulos en GRUB o usar un disco físico/partición/unidad flash en una máquina virtual en lugar de una imagen.
Una vez exitosa esta etapa, comienza el verdadero desarrollo.
Proporcionar soporte en tiempo de ejecución
![](https://i0.wp.com/habrastorage.org/getpro/habr/post_images/3f8/0b4/0e4/3f80b40e4764b86c5916ddda3bf8689f.jpg)
- función para asignar dinámicamente un bloque de datos en la pila
- trabajando con montón
- función de copia de bloque de datos (memcpy)
- La función es el punto de entrada al programa.
- llamadas a constructores y destructores de objetos globales
- una serie de funciones para trabajar con excepciones
- stub para funciones virtuales puras no realizadas
Naturalmente, también deberíamos mencionar la biblioteca estándar. No es necesaria una implementación completa, pero vale la pena implementar un subconjunto central de la funcionalidad. Entonces escribir código será mucho más familiar y rápido.
Depuración
![](https://i2.wp.com/habrastorage.org/getpro/habr/post_images/fae/161/ecb/fae161ecb14d5045044c21497fe891af.jpg)
Podemos recomendar lo siguiente:
- No hace falta decirlo, salida de depuración.
- afirmar con salida inmediata al "depurador" (ver el siguiente párrafo)
- algo parecido a un depurador de consola
- comprueba si el emulador te permite conectar un depurador, tablas de símbolos u otra cosa
- parte de las operaciones de depuración estándar: puntos de interrupción, pila de llamadas, impresión de valores, impresión de volcados, ...
- comandos para mostrar información útil, como la cola de ejecución del programador o varias estadísticas (no es tan inútil como podría parecer al principio)
- Comandos para comprobar la coherencia del estado de varias estructuras: listas de memoria libre/utilizada, montón o cola de mensajes.
Desarrollo
![](https://i0.wp.com/habrastorage.org/getpro/habr/post_images/09f/846/1a6/09f8461a63611d23d3d903ecccfa7c5f.jpg)
SDK similares
![](https://i0.wp.com/habrastorage.org/getpro/habr/post_images/3c4/b7e/555/3c4b7e555c36a424cd9c5ae5e4da2b4b.jpg)
No es necesaria documentación que describa el proceso de redacción de tal o cual tipo de programa. Pero vale la pena hacer un espacio en blanco a partir de elementos estándar. Esto no sólo hará que sea más fácil agregar programas (lo que se puede hacer copiando programas existentes y luego cambiándolos, pero esto llevará más tiempo), sino que también hará que sea más fácil actualizarlos cuando cambien las interfaces, los formatos o cualquier otra cosa. Está claro que, idealmente, tales cambios no deberían ocurrir, pero como el desarrollo de sistemas operativos es algo atípico, hay muchos lugares para tomar decisiones potencialmente equivocadas. Pero la comprensión de la falacia de las decisiones tomadas, como siempre, llegará algún tiempo después de su implementación.
Próximos pasos
![](https://i1.wp.com/habrastorage.org/getpro/habr/post_images/20e/1bf/07c/20e1bf07c533f75a8979ca8e7ebeb9ba.jpg)
Original: AsmSchool: Crea un sistema operativo
Autor: Mike Saunders
Fecha de publicación: 15 de abril de 2016
Traducción: A. Panin
Fecha de traducción: 16 de abril de 2016
Parte 4: Con las habilidades que has adquirido al leer los artículos anteriores de esta serie, ¡puedes empezar a desarrollar tu propio sistema operativo!
¿Para qué es esto?
- Comprender cómo funcionan los compiladores.
- Comprender las instrucciones de la CPU.
- Para optimizar su código para el rendimiento.
A lo largo de varios meses, recorrimos un camino complejo que comenzó con el desarrollo de programas simples en lenguaje ensamblador para Linux y terminó en el último artículo de la serie con el desarrollo de un código autónomo que se ejecuta en una computadora personal sin un sistema operativo. Bueno, ahora intentaremos recopilar toda la información y crear un sistema operativo real. Sí, seguiremos los pasos de Linus Torvalds, pero primero debemos responder las siguientes preguntas: “¿Qué es un sistema operativo? ¿Cuáles de sus funciones tendremos que recrear?”
En este artículo nos centraremos únicamente en las funciones básicas del sistema operativo: cargar y ejecutar programas. Los sistemas operativos complejos realizan muchas más funciones, como administrar memoria virtual y procesamiento de paquetes de red, pero su correcta implementación requiere años de trabajo continuo, por lo que en este artículo consideraremos solo las funciones básicas presentes en cualquier sistema operativo. El mes pasado desarrollamos un pequeño programa que encaja en el sector de 512 bytes de un disquete (su primer sector), y ahora lo modificaremos un poco para agregar la función de cargar datos adicionales desde el disco.
Desarrollo del cargador de arranque
Podríamos intentar reducir al máximo el tamaño del código binario de nuestro sistema operativo para que quepa en el primer sector de 512 bytes del disquete, el que carga la BIOS, pero en este caso no podríamos para implementar funciones interesantes. Por lo tanto, usaremos estos 512 bytes para alojar el código binario de un gestor de arranque simple que cargará el código binario del kernel del sistema operativo en RAM y cumplirlo. (Después de esto, desarrollaremos el kernel del sistema operativo, que cargará el código binario de otros programas desde el disco y también lo ejecutará, pero hablaremos de esto un poco más adelante).
Puede descargar el código fuente de los ejemplos analizados en este artículo en www.linuxvoice.com/code/lv015/asmschool.zip. Y este es el código de nuestro gestor de arranque del sistema desde un archivo llamado boot.asm:
BITS 16 jmp inicio corto; Saltar a la etiqueta, omitiendo la descripción del disco nop; Adición antes de la descripción del disco %include "bpb.asm" start: mov ax, 07C0h; Cargar dirección mov ds, ax; Segmento de datos mov ax, 9000h; Preparación de pila mov ss, ax mov sp, 0FFFFh; ¡La pila crece hacia abajo! cld; Estableciendo el indicador de dirección mov si, kern_filename call load_file jmp 2000h:0000h; Transición al código binario del kernel del sistema operativo cargado desde el archivo kern_filename db "MYKERNELBIN" %include "disk.asm" multiplicado por 510-($-$$) db 0; Rellenar el código binario con ceros hasta 510 bytes dw 0AA55h; Búfer de marcador de final binario del cargador de arranque: ; Inicio del búfer para el contenido del disco
En este código, la primera instrucción de la CPU es la instrucción jmp, que se encuentra después de la directiva BITS, que le indica al ensamblador NASM que se está utilizando el modo de 16 bits. Como probablemente recordará del artículo anterior de la serie, la ejecución del código binario de 512 bytes cargado desde el BIOS desde el disco comienza desde el principio, pero tenemos que saltar a una etiqueta para omitir un conjunto especial de datos. Obviamente, el mes pasado simplemente escribimos el código al principio del disco (usando la utilidad dd) y dejamos el resto del espacio vacío.
Ahora tendremos que usar un disquete con un sistema de archivos MS-DOS adecuado (FAT12) y, para que funcione correctamente con este sistema de archivos, debemos agregar un conjunto de datos especiales cerca del comienzo del sector. Este conjunto se denomina bloque de parámetros del BIOS (BPB) y contiene datos como la etiqueta del disco, la cantidad de sectores, etc. No debería interesarnos en esta etapa, ya que dichos temas podrían dedicarse a más de una serie de artículos, por lo que hemos colocado todas las instrucciones y datos asociados con ellos en un archivo de código fuente separado llamado bpb.asm.
Con base en lo anterior, esta directiva de nuestro código es extremadamente importante:
% incluye "bpb.asm"
Esta es una directiva NASM que permite que el contenido de un archivo fuente específico se incluya en el archivo fuente actual durante el ensamblaje. De esta manera podemos hacer que el código de nuestro gestor de arranque sea lo más breve y comprensible posible colocando todos los detalles de la implementación del bloque de parámetros del BIOS en un archivo separado. El bloque de parámetros del BIOS debe ubicarse tres bytes después del inicio del sector, y dado que la instrucción jmp ocupa solo dos bytes, tenemos que usar la instrucción nop (su nombre significa "sin operación"; esta es una instrucción que no nada más que desperdiciar ciclos de CPU) para llenar el byte restante.
Trabajando con la pila
A continuación, tendremos que utilizar instrucciones similares a las comentadas en el último artículo para preparar los registros y la pila, así como la instrucción cld (que significa "dirección clara"), que nos permite configurar el indicador de dirección para determinadas instrucciones. como la instrucción lodsb, que, cuando se ejecuta, incrementará el valor en el registro SI en lugar de disminuirlo.
Después de eso, colocamos la dirección de la cadena en el registro SI y llamamos a nuestra función load_file. Pero piénselo por un minuto: ¡aún no hemos desarrollado esta función! Sí, esto es cierto, pero su implementación se puede encontrar en otro archivo de código fuente que incluimos llamado disk.asm.
El sistema de archivos FAT12, utilizado en disquetes formateados en MS-DOS, es uno de los sistemas de archivos más simples disponibles, pero también requiere una cantidad considerable de código para trabajar con su contenido. La subrutina load_file tiene aproximadamente 200 líneas y no se mostrará en este artículo, ya que estamos considerando el proceso de desarrollo de un sistema operativo, no un controlador para un sistema de archivos específico, por lo que no es muy inteligente desperdiciar espacio en el registrar páginas de esta manera. En general, incluimos el archivo de código fuente disk.asm casi antes del final del archivo de código fuente actual y podemos olvidarnos de él. (Si todavía está interesado en la estructura del sistema de archivos FAT12, puede familiarizarse con excelente reseña en http://tinyurl.com/fat12spec, luego mire el archivo de código fuente disk.asm; el código que contiene está bien comentado).
En cualquier caso, la rutina load_file carga el código binario del archivo nombrado en el registro SI en el segmento 2000 en el desplazamiento 0, después de lo cual saltamos al principio para su ejecución. Y eso es todo: ¡el kernel del sistema operativo está cargado y el gestor de arranque del sistema ha completado su tarea!
Es posible que haya notado que nuestro código usa MYKERNELBIN en lugar de MYKERNEL.BIN como nombre de archivo del kernel del sistema operativo, lo que encaja bien en el esquema de nombres 8+3 utilizado en los disquetes en DOS. De hecho, el sistema de archivos FAT12 utiliza una representación interna de nombres de archivos, y ahorramos espacio al usar un nombre de archivo que garantiza que no requerirá que nuestra rutina load_file implemente un mecanismo para buscar el carácter de punto y convertir el nombre de archivo a la representación interna de el sistema de archivos.
Después de la línea con la directiva para conectar el archivo de código fuente disk.asm, hay dos líneas diseñadas para rellenar el código binario del cargador de arranque del sistema con ceros de hasta 512 bytes e incluir la marca de final de su código binario (esto se discutió en el artículo anterior). Finalmente, al final del código está la etiqueta "búfer", que utiliza la rutina load_file. Básicamente, la rutina load_file necesita espacio libre en la RAM para realizar algún trabajo intermedio mientras busca un archivo en el disco, y tenemos mucho espacio libre después de cargar el cargador de arranque, por lo que colocamos el búfer aquí.
Para ensamblar el cargador de arranque del sistema, use el siguiente comando:
Nasm -f bin -o arranque.bin arranque.asm
Ahora necesitamos crear una imagen de disquete virtual en formato MS-DOS y agregar el código binario de nuestro gestor de arranque a sus primeros 512 bytes usando los siguientes comandos:
Mkdosfs -C floppy.img 1440 dd conv=notrunc if=boot.bin of=floppy.img
¡En este punto, el proceso de desarrollo de un cargador de arranque del sistema puede considerarse completo! Ahora tenemos una imagen de disquete de arranque que nos permite cargar el código binario del kernel del sistema operativo desde un archivo llamado mykernel.bin y ejecutarlo. A continuación nos espera una parte más interesante del trabajo: el desarrollo del propio núcleo del sistema operativo.
Núcleo del sistema operativo
Queremos que el núcleo de nuestro sistema operativo realice muchas tareas importantes: mostrar un mensaje de saludo, aceptar entradas del usuario, determinar si la entrada es un comando compatible y ejecutar programas desde el disco cuando el usuario especifica sus nombres. Este es el código del kernel del sistema operativo del archivo mykernel.asm:
Mov ax, 2000h mov ds, ax mov es, ax loop: mov si, llamada rápida lib_print_string mov si, user_input llamada lib_input_string cmp byte, 0 bucle cmp palabra, "ls" y list_files mov ax, si mov cx, 32768 llamada lib_load_file jc load_fail llamada 32768 jmp loop load_fail: mov si, load_fail_msg llamada lib_print_string jmp loop list_files: mov si, file_list llamada lib_get_file_list llamada lib_print_string jmp loop símbolo db 13, 10, "MyOS >", 0 load_fail_msg db 13, 10, "¡No encontrado! ", 0 entrada de usuario multiplicada por 256 db 0 lista de archivos multiplicada por 1024 db 0 %incluye "lib.asm"
Antes de mirar el código, debes prestar atención a la última línea con la directiva para incluir el archivo de código fuente lib.asm, que también se encuentra en el archivo asmschool.zip de nuestro sitio web. Esta es una biblioteca de rutinas útiles para trabajar con la pantalla, el teclado, las cadenas y los discos que también puede usar; en este caso incluimos este archivo de código fuente al final del archivo de código fuente principal del kernel del sistema operativo para para que este último sea lo más compacto y bonito posible. Consulte la sección Rutinas de la biblioteca lib.asm para información adicional sobre todas las subrutinas disponibles.
En las primeras tres líneas del código del kernel del sistema operativo, llenamos los registros de segmento con datos para señalar el segmento 2000 en el que se cargó el código binario. Esto es importante para asegurar el correcto funcionamiento de instrucciones como lodsb, que deben leer datos del segmento actual y no de otro. Después de esto, no realizaremos ninguna operación adicional en los segmentos; ¡Nuestro sistema operativo funcionará con 64 KB de RAM!
Más adelante en el código hay una etiqueta correspondiente al comienzo del bucle. En primer lugar, utilizamos una de las rutinas de la biblioteca lib.asm, concretamente lib_print_string, para imprimir el saludo. Los bytes 13 y 10 antes de la línea de saludo son caracteres de escape. nueva linea, gracias a lo cual el saludo no se mostrará inmediatamente después de la salida de ningún programa, sino siempre en una nueva línea.
Después de esto, usamos otra rutina de la biblioteca lib.asm llamada lib_input_string, que toma la entrada del teclado del usuario y la almacena en un búfer apuntado en el registro SI. En nuestro caso, el búfer se declara cerca del final del código del kernel del sistema operativo de la siguiente manera:
Tiempos de entrada de usuario 256 db 0
Esta declaración le permite crear un búfer de 256 caracteres, lleno de ceros; ¡su longitud debería ser suficiente para almacenar comandos para un sistema operativo simple como el nuestro!
A continuación realizamos la validación de la entrada del usuario. Si el primer byte del búfer user_input es cero, entonces el usuario simplemente presionó la tecla Enter sin ingresar ningún comando; No olvide que todas las cadenas terminan con caracteres nulos. Entonces, en este caso deberíamos ir al principio del bucle e imprimir el saludo nuevamente. Sin embargo, en caso de que el usuario ingrese algún comando, primero tendremos que verificar si ingresó el comando ls. Hasta ahora en nuestros programas en lenguaje ensamblador sólo se podían observar comparaciones de bytes individuales, pero no olvide que también es posible comparar valores de doble byte o palabras de máquina. En este código, comparamos la primera palabra de máquina del búfer user_input con la palabra de máquina correspondiente a la línea ls y, si son idénticas, pasamos al bloque de código siguiente. Dentro de este bloque de código, usamos otra rutina de lib.asm para obtener una lista de archivos separados por comas en el disco (que deben almacenarse en el búfer file_list), imprimimos esa lista en la pantalla y regresamos al bucle para procesar la entrada del usuario.
Ejecución de programas de terceros.
Si el usuario no ingresa el comando ls, asumimos que ingresó el nombre del programa desde el disco, por lo que tiene sentido intentar cargarlo. Nuestra biblioteca lib.asm contiene una implementación de la útil rutina lib_load_file, que analiza las tablas del sistema de archivos FAT12 del disco: acepta un puntero al comienzo de una línea de nombre de archivo a través del registro AX, así como un valor de desplazamiento para cargar código binario desde un archivo de programa a través del registro CX. Ya usamos el registro SI para almacenar un puntero a la cadena que contiene la entrada del usuario, por lo que copiamos este puntero al registro AX y luego colocamos el valor 32768, que se usa como desplazamiento para cargar el código binario desde el archivo del programa. en el registro CX.
Pero, ¿por qué utilizamos este valor particular como desplazamiento para cargar código binario desde un archivo de programa? Pues esta es sólo una de las opciones de mapas de memoria de nuestro sistema operativo. Debido a que estamos trabajando en un solo segmento de 64 KB y nuestro binario del kernel está cargado en el desplazamiento 0, tenemos que usar los primeros 32 KB de memoria para los datos del kernel y los 32 KB restantes para cargar los datos del programa. Por lo tanto, el offset 32768 es el medio de nuestro segmento y nos permite proporcionar suficiente RAM tanto para el kernel del sistema operativo como para los programas cargados.
La rutina lib_load_file luego realiza una operación muy importante: si no puede encontrar un archivo con el nombre dado en el disco, o por alguna razón no puede leerlo del disco, simplemente sale y establece un indicador de transporte especial. Este es un indicador de estado de la CPU que se establece durante la ejecución de algunas operaciones matemáticas y no debería interesarnos por el momento, pero al mismo tiempo podemos determinar la presencia de este indicador para tomar decisiones rápidas. Si la rutina lib_load_asm establece el indicador de acarreo, usamos la instrucción jc (saltar si acarrea) para saltar a un bloque de código que imprime el mensaje de error y regresa al comienzo del ciclo de entrada del usuario.
En el mismo caso, si el indicador de transferencia no está configurado, podemos concluir que la subrutina lib_load_asm ha cargado exitosamente el código binario del archivo de programa en la RAM en la dirección 32768. Todo lo que necesitamos en este caso es iniciar la ejecución del binario. código cargado en esta dirección, es decir, ¡comience a ejecutar el programa especificado por el usuario! Y después de usar la instrucción ret en este programa (para regresar al código de llamada), simplemente necesitaremos regresar al bucle para procesar la entrada del usuario. Así creamos un sistema operativo: consta de los mecanismos más simples de análisis de comandos y carga de programas, implementados en aproximadamente 40 líneas de código ensamblador, aunque con mucha ayuda de rutinas de la biblioteca lib.asm.
Para ensamblar el código del kernel del sistema operativo, use el siguiente comando:
Nasm -f bin -o mykernel.bin mykernel.asm
Después de esto tendremos que agregar de alguna manera el archivo mykernel.bin al archivo de imagen del disquete. Si está familiarizado con el truco de montar imágenes de disco usando dispositivos loopback, puede acceder al contenido de la imagen de disco usando floppy.img, pero hay una manera más sencilla usando GNU Mtools (www.gnu.org/software/mtools). Este es un conjunto de programas para trabajar con disquetes usando sistemas de archivos MS-DOS/FAT12, disponibles en los repositorios de paquetes de software de todas las distribuciones populares de Linux, por lo que solo necesita usar apt-get, yum, pacman o cualquier otra utilidad. Se utiliza para instalar paquetes de software en su distribución.
Después de instalar el paquete de software apropiado, deberá ejecutar el siguiente comando para agregar el archivo mykernel.bin al archivo de imagen del disco floppy.img:
Mcopia -i floppy.img mykernel.bin::/
Tenga en cuenta los símbolos divertidos al final del comando: dos puntos, dos puntos y barra diagonal. Ahora ya casi estamos listos para lanzar nuestro sistema operativo, pero ¿de qué sirve si no hay aplicaciones para ello? Corrijamos este malentendido desarrollando una aplicación extremadamente sencilla. Sí, ahora desarrollarás una aplicación para tu propio sistema operativo; imagínate cuánto aumentará tu autoridad entre las filas de los geeks. Guarde el siguiente código en un archivo llamado test.asm:
Org 32768 mov ah, 0Eh mov al, "X" int 10h ret
Este código simplemente usa la función BIOS para imprimir una "X" en la pantalla y luego devuelve el control al código que lo llamó; en nuestro caso, ese código es el código del sistema operativo. La línea de organización que comienza el código fuente de la aplicación no es una instrucción de la CPU, sino una directiva del ensamblador NASM que le indica que el código binario se cargará en la RAM en el desplazamiento 32768 y, por lo tanto, todos los desplazamientos deben recalcularse para tener en cuenta esto.
Este código también debe ensamblarse y el archivo binario resultante debe agregarse al archivo de imagen del disquete:
Nasm -f bin -o test.bin test.asm mcopy -i floppy.img test.bin::/
Ahora respire hondo, prepárese para contemplar los incomparables resultados de su propio trabajo e inicie la imagen del disquete usando un emulador de PC como Qemu o VirtualBox. Por ejemplo, se puede utilizar el siguiente comando para este propósito:
Qemu-system-i386 -fda floppy.img
Voila: el gestor de arranque del sistema boot.img, que integramos en el primer sector de la imagen del disco, carga el kernel del sistema operativo mykernel.bin, que muestra un mensaje de bienvenida. Ingrese el comando ls para obtener los nombres de dos archivos ubicados en el disco (mykernel.bin y test.bin), y luego ingrese el nombre del último archivo para ejecutarlo y mostrar una X en la pantalla.
Es genial, ¿no? Ahora puede comenzar a modificar el shell de comandos de su sistema operativo, agregar implementaciones de nuevos comandos y también agregar archivos de programas adicionales al disco. Si desea ejecutar este sistema operativo en una PC real, debe consultar la sección "Ejecutar el gestor de arranque en una plataforma de hardware real" del artículo anterior de la serie; necesitará exactamente los mismos comandos. El próximo mes, haremos que nuestro sistema operativo sea más poderoso al permitir que los programas descargables utilicen funciones del sistema, introduciendo un concepto de código compartido para reducir la duplicación de código. Gran parte del trabajo aún está por delante.
rutinas de la biblioteca lib.asm
Como se mencionó anteriormente, la biblioteca lib.asm proporciona un gran conjunto de rutinas útiles para usar dentro de los núcleos y sistemas operativos de su sistema operativo. programas individuales. Algunos de ellos utilizan instrucciones y conceptos que aún no se han abordado en los artículos de esta serie, otros (como las rutinas de disco) están estrechamente relacionados con el diseño de sistemas de archivos, pero si se considera competente en estos asuntos, puede leer Compruébelos usted mismo con sus implementaciones y comprenda el principio de funcionamiento. Sin embargo, es más importante descubrir cómo llamarlos desde su propio código:
- lib_print_string: acepta un puntero a una cadena terminada en nulo a través del registro SI e imprime la cadena en la pantalla.
- lib_input_string: acepta un puntero a un búfer a través del registro SI y llena este búfer con caracteres ingresados por el usuario usando el teclado. Después de que el usuario presiona la tecla Enter, la línea en el buffer termina con un carácter nulo y el control regresa al código del programa de llamada.
- lib_move_cursor: mueve el cursor en la pantalla a una posición con coordenadas transmitidas a través de los registros DH (número de fila) y DL (número de columna).
- lib_get_cursor_pos: esta subrutina debe llamarse para obtener los números de fila y columna actuales utilizando los registros DH y DL, respectivamente.
- lib_string_uppercase: toma un puntero al comienzo de una cadena terminada en nulo utilizando el registro AX y convierte los caracteres de la cadena a mayúsculas.
- lib_string_length: toma un puntero al comienzo de una cadena terminada en nulo a través del registro AX y devuelve su longitud a través del registro AX.
- lib_string_compare: acepta punteros al comienzo de dos cadenas terminadas en nulo utilizando los registros SI y DI y compara estas cadenas. Establece el indicador de acarreo si las líneas son idénticas (para usar una instrucción de salto dependiendo del indicador de acarreo jc) o borra este indicador si las líneas son diferentes (para usar la instrucción jnc).
- lib_get_file_list: toma un puntero al inicio de un búfer a través del registro SI y coloca en ese búfer una cadena terminada en nulo que contiene una lista separada por comas de nombres de archivos del disco.
- lib_load_file: toma un puntero al comienzo de una línea que contiene el nombre del archivo a través del registro AX y carga el contenido del archivo en el desplazamiento pasado a través del registro CX. Devuelve el número de bytes copiados en la memoria (es decir, el tamaño del archivo) utilizando el registro BX, o establece el indicador de transporte si no se encuentra un archivo con el nombre indicado.