Continuamos esta serie de posts sobre cómo crear nuestro sistema operativo. Hoy no nos vamos a centrar en un tema sino que vamos a definir algunas funciones útiles de ahora en adelante. En primer lugar vamos a definir 3 funciones que cumplan la función de memcpy, memset y memcmp:
void* ND::Memory::Set(void* buf, int c, size_t len)
{
unsigned char* tmp=(unsigned char*)buf;
while(len--)
{
*tmp++=c;
}
return buf;
}
void* ND::Memory::Copy(void* dest,const void* src, size_t len)
{
const unsigned char* sp=(const unsigned char*)src;
unsigned char* dp=(unsigned char*)dest;
for(;len!=0;len--) *dp++=*sp++;
return dest;
}
int ND::Memory::Compare(const void* p1, const void* p2, size_t len)
{
const char* a=(const char*)p1;
const char* b=(const char*)p2;
size_t i=0;
for(;i<len;i++)
{
if(a[i] < b[i]) return -1; else if(a[i] > b[i])
return 1;
}
return 0;
}
Todas ellas se auto-implementan. Estas funciones yo las he sacado de una pequeña librería del C, la implementación suele ser parecida en todos los sistemas operativos. Ahora vamos a hacer 3 funciones simulares pero para manipular strings. Cumplirían la función de strcpy, strcat y strcmp.
size_t ND::String::Length(const char* src)
{
size_t i=0;
while(*src--)
i++;
return i;
}
int ND::String::Copy(char* dest, const char* src)
{
int n = 0;
while (*src)
{
*dest++ = *src++;
n++;
}
*dest = '';
return n;
}
int ND::String::Compare(const char *p1, const char *p2)
{
int i = 0;
int failed = 0;
while(p1[i] != '' && p2[i] != '')
{
if(p1[i] != p2[i])
{
failed = 1;
break;
}
i++;
}
if( (p1[i] == '' && p2[i] != '') || (p1[i] != '' && p2[i] == '') )
failed = 1;
return failed;
}
char *ND::String::Concatenate(char *dest, const char *src)
{
int di = ND::String::Length(dest);
int si = 0;
while (src[si])
dest[di++] = src[si++];
dest[di] = '';
return dest;
}
Vamos ahora con unas funciones bastante interesantes. Con estas funciones podremos leer y escribir en los puertos del hardware. Esto normalmente se hace con ASM y corresponde (en x86) a las instrucciones in y out. Para llamar de una manera fácil a ASM desde C se usa la instrucción asm, con el peligro que conlleva de que no es portable. A esta sentencia le añadimos el volatile para que GCC no intente optimizar ese texto. Por otra parte la instrucción asm tiene una forma curiosa de aceptar parámetros, pero eso creo que se entiende mejor viendo los ejemplos.
void ND::Ports::OutputB(uint16_t port, uint8_t value)
{
asm volatile("outb %1, %0" : : "dN"(port), "a"(value));
}
uint8_t ND::Ports::InputB(uint16_t _port)
{
unsigned char rv;
asm volatile("inb %1, %0" : "=a"(rv) : "dN"(_port));
return rv;
}
Y hasta aquí el post 3, hoy no hemos hecho nada vistoso pero sí hemos definido una funciones que nos vendrán bien de cara a un futuro. Aviso a los usuarios de 64 bits que estoy trabajando en solucionar un bug que impide compilar correctamente en 64 bits. En el siguiente post veremos un componente importante de la arquitectura x86, la GDT.
9 comentarios, deja el tuyo
A ver si comentas eso de como solucionar el bug, porque no avanzo desde la primera parte.
Creo que comentaron el error en los comentarios del post 2. Creian que era algo del grub si no recuerdo mal
Copiar bye a bye?
Byte a byte evidentemente…, 2 fallos consecutivos no hacen un acierto, confirmado.
Muchas gracias por el post muy bueno y lo estoy siguiendo, tengo algunas prguntas:
1. Cuando dices ‘Para llamar de una manera fácil a ASM desde C se usa la instrucción asm, con el peligro que conlleva de que no es portable’, a que te refieres cuando dices ‘con el peligro que conlleva de que no es portable’?
2. Si se fuera a hacer un sistema operativo ‘profesional'(por decirlo de alguna manera) esta parte de acceder al Hardware se haría en Ensamblador.
3. Como se haría en Ensamblador.?
Gracias a ti por seguirlo, voy a contestarte a las preguntas de una en una:
1- Bien, el problema de la instrucción asm es que no existe en ningún estándar C, por ello cada compilador lo implementa a su manera (si es que lo implementa). En este caso se puede compilar con GCC y Clang(se parece mucho a GCC en este aspecto) pero no podrás en otros compiladores como Intel C (este usa la Intel Syntax en el ASM).
2- Profesionalmente se debería separar claramente una parte y otra por arquitecturas y otra parte común. No es necesario hacerlo en ensamblador (Linux lo tiene dentro de C)
3- En ensamblador es muy sencillo también, pero lo tienes en un archivo aparte. Por otro lado al tener argumentos primero debemos pasarlo a los registros y eso puede liar un poco más, después se invoca a outb o inb y se declara la función como visible globalmente. Luego desde C deberás hacer un header que declare una función «extern». En NASM (Intel Syntax) sería algo así:
outb:
push ebp
mov ebp, esp
mov eax, [ebp + 12]
mov edx, [ebp + 8]
out dx, al
mov esp, ebp
pop ebp
ret
Pregunta, la sintaxis de que asm usas? No entiendo porque tantos asm distintos xD MASM, FASM, NASM, AT&T…
Y si tenes tiempo, podrias explicar la linea:
asm volatile(«outb %1, %0» : : «dN»(port), «a»(value));
Hasta «asm volatile» entendi xD «outbyte 1,0?»
O es 1–>»dN»(port), 0 –> «a»(value)?
Si es esta ultima, no entiendo que es «dn» y que es «a»…
Muchas gracias por tus aportess! Increibless!!
La sintaxis que uso es AT&T que es la que usa GCC internamente aunque se puede hacer sin problemas en NASM (adaptando la sintaxis). Respecto a esa sentencia compleja de asm volatile solo puedo decirte que es así por el GCC ya que usa unos parámetros para saber como debe pasar el dato. Por ejemplo «a» es un operando especial para x86 que se usa para representar el registro a. La lista entera está aquí: http://gcc.gnu.org/onlinedocs/gcc/Constraints.html#Constraints
bien, realmente necesitare ayuda, soy nuevo en esto y no tengo ni la mas minima idea de que hacer con lo escrito en el terminal ¿alguien me ayuda?