Hace 70 años, el 16 de diciembre de 1947, en los laboratorios Bell Labs, John Bardin y Walter Brattain, bajo la dirección de William Shockley, crearon el primer transistor bipolar operativo. El 23 de diciembre, Brattain demostró a sus colegas el primer amplificador de transistores. Por lo tanto, este día a menudo se llama Día del Transistor .
No hay necesidad de hablar sobre la importancia de este evento. El transistor se considera uno de los inventos más importantes del siglo XX, sin el cual las computadoras seguirían funcionando en lámparas y relés, y ocuparían edificios enteros. Shockley, Bardin y Brattain recibieron el Premio Nobel de Física por su trabajo en 1956. Con los años, el transistor se ha miniaturizado a solo unos pocos átomos. Cada procesador tiene miles de millones de transistores, por lo que el transistor puede llamarse el dispositivo más masivo creado por la humanidad.Pero, ¿qué tipo de trabajo hace el transistor por nosotros? Hagamos un viaje mental: trazaremos el camino desde algunas yemas de los dedos de alto nivel hasta nuestro cumpleaños: transistor.¿Qué tomar como punto de partida? Bueno, al menos dibuja un botón de habrakat.HTML y CSS
Un botón consta de píxeles de fondo, texto y borde. En el código, establecido por la etiqueta <a>, a la que se aplican las reglas de diseño CSS. Por ejemplo, una regla CSS se aplica a un borde en las esquinas redondeadas:border-radius: 3px;
Por lo tanto, el límite consta de cuatro segmentos y cuatro arcos ("cuartos" de un círculo).Navegador
Para la investigación, tomé mi Firefox favorito. Antes de que FF comience a dibujar nuestro botón, necesita hacer mucho trabajo para analizar y calcular la posición de los elementos:- Descargar HTML a través de una red, analizar, componer un árbol DOM
- Descargar a través de la red CSS, realizar análisis léxico, analizar
- Enlazar reglas basadas en prioridad y herencia a elementos de página
- Para todos los nodos DOM visibles, componga un árbol de sus áreas rectangulares: marcos.
- Para marcos, calcule dimensiones y ubicación (ver video )
- Componga capas a partir de cuadros teniendo en cuenta el índice zy el tipo de contenido (<canvas>, SVG, <video>).
- Cree una lista de dibujo en orden: color de fondo, imagen de fondo, borde, descendientes, contorno.
Añadir materiales de lectura: No nos detendremos en estos pasos en detalle. Después de ellos viene el dibujo real de los elementos necesarios.Descargue la fuente para averiguar qué está sucediendo allí,Mozilla Firefox.
Firefox Mercurial Visual Studio C++. VS
symbols.mozilla.org. - /layout/.
, , , Firefox. c , , — FF.
El archivo nsCSSRenderingBorders.cpp es responsable de dibujar los bordes . Y la función general de dibujar bordes se llama (quién hubiera pensado): DrawBorders () . La función selecciona el método de representación óptimo para diversas situaciones. Tenemos un caso relativamente simple: hay un radio de borde, pero los bordes en todos los lados son sólidos y del mismo color.Nuestro siif (allBordersSame &&
mCompositeColors[0] == nullptr &&
mBorderStyles[0] == NS_STYLE_BORDER_STYLE_SOLID &&
!mAvoidStroke &&
!mNoBorderRadius)
{
gfxRect outerRect = ThebesRect(mOuterRect);
RoundedRect borderInnerRect(outerRect, mBorderRadii);
borderInnerRect.Deflate(mBorderWidths[eSideTop],
mBorderWidths[eSideBottom],
mBorderWidths[eSideLeft],
mBorderWidths[eSideRight]);
RefPtr<PathBuilder> builder = mDrawTarget->CreatePathBuilder();
AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true);
AppendRoundedRectToPath(builder, ToRect(borderInnerRect.rect), borderInnerRect.corners, false);
RefPtr<Path> path = builder->Finish();
mDrawTarget->Fill(path, color);
return;
}
Hay opciones mucho más complejas, como el acoplamiento en esquinas con radio de borde de diferentes tipos de bordes punteados y punteados, consulte DrawDashedOrDottedCorner () . Hay en código completamentePero volvamos a nuestro si. Del comentario aprendemos que, en este caso, el borde se dibuja utilizando dos rectángulos: interno y externo, luego el camino creado (camino) se llena con el color deseado.AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true);
AppendRoundedRectToPath(builder, ToRect(borderInnerRect.rect), borderInnerRect.corners, false);
RefPtr<Path> path = builder->Finish();
mDrawTarget->Fill(path, color);
Vaya a AppendRoundedRectToPath () en gfx / 2d / PathHelpers.cpp.Nuevamente establecemos puntos de interrupción Del comentario sobre la función aprendemos que las curvas de Bezier dibujan las esquinas en cuatro puntos de control . Las curvas de Bezier a menudo se usan en gráficos de computadora, incluso para dibujar arcos de círculos y elipses. A medida que aprendemos más del comentario, hay muchas opciones para elegir puntos de control para construir una curva. En este caso, necesitamos que los puntos 0 y 3 pertenezcan a los lados del rectángulo, los puntos 0, 1 y C se encuentran en una línea recta, los puntos 3, 2 y C en el otro. Vea la figura:
nos queda calcular la relación de las longitudes de los segmentos 01 / 0C y 32 / 3C. Aquí los autores usan cálculos aproximados y obtienen la constante mágica alfa:const Float alpha = Float(0.55191497064665766025);
Desafortunadamente, los artículos con el algoritmo de selección de punto de control al que hace referencia el comentario no son de dominio público. Pero en general, debe tenerse en cuenta que en los gráficos por computadora, los algoritmos a menudo usan aproximaciones para mejorar el rendimiento. Por ejemplo, el algoritmo Brezenham le permite dibujar segmentos y círculos no "en la frente", resolviendo las ecuaciones y = f (x), sino con operaciones enteras más astutas. Lo mismo con relleno, etc.Más adelante en el ciclo vamos de esquina a esquina, usamos alfa para calcular las coordenadas de los puntos de control y, finalmente, llamamos a las funciones de dibujar la línea del borde y el arco de la esquina:aPathBuilder->LineTo(p0);
aPathBuilder->BezierTo(p1, p2, p3);
Añadir material de lectura Código completo AppendRoundedRectToPath ()void
AppendRoundedRectToPath(PathBuilder* aPathBuilder,
const Rect& aRect,
const RectCornerRadii& aRadii,
bool aDrawClockwise)
{
const Float alpha = Float(0.55191497064665766025);
typedef struct { Float a, b; } twoFloats;
twoFloats cwCornerMults[4] = { { -1, 0 },
{ 0, -1 },
{ +1, 0 },
{ 0, +1 } };
twoFloats ccwCornerMults[4] = { { +1, 0 },
{ 0, -1 },
{ -1, 0 },
{ 0, +1 } };
twoFloats *cornerMults = aDrawClockwise ? cwCornerMults : ccwCornerMults;
Point cornerCoords[] = { aRect.TopLeft(), aRect.TopRight(),
aRect.BottomRight(), aRect.BottomLeft() };
Point pc, p0, p1, p2, p3;
if (aDrawClockwise) {
aPathBuilder->MoveTo(Point(aRect.X() + aRadii[RectCorner::TopLeft].width,
aRect.Y()));
} else {
aPathBuilder->MoveTo(Point(aRect.X() + aRect.Width() - aRadii[RectCorner::TopRight].width,
aRect.Y()));
}
for (int i = 0; i < 4; ++i) {
int c = aDrawClockwise ? ((i+1) % 4) : ((4-i) % 4);
int i2 = (i+2) % 4;
int i3 = (i+3) % 4;
pc = cornerCoords[c];
if (aRadii[c].width > 0.0 && aRadii[c].height > 0.0) {
p0.x = pc.x + cornerMults[i].a * aRadii[c].width;
p0.y = pc.y + cornerMults[i].b * aRadii[c].height;
p3.x = pc.x + cornerMults[i3].a * aRadii[c].width;
p3.y = pc.y + cornerMults[i3].b * aRadii[c].height;
p1.x = p0.x + alpha * cornerMults[i2].a * aRadii[c].width;
p1.y = p0.y + alpha * cornerMults[i2].b * aRadii[c].height;
p2.x = p3.x - alpha * cornerMults[i3].a * aRadii[c].width;
p2.y = p3.y - alpha * cornerMults[i3].b * aRadii[c].height;
aPathBuilder->LineTo(p0);
aPathBuilder->BezierTo(p1, p2, p3);
} else {
aPathBuilder->LineTo(pc);
}
}
aPathBuilder->Close();
}
Pero todo depende del backend de los gráficos 2D que usa Mozilla.Motor de gráficos
Gecko usa la biblioteca Moz2D independiente de la plataforma, que a su vez puede usar uno de los backends: Cairo, Skia, Direct2D, Quartz y NV Path. Por ejemplo, Direct2D, Cairo, Skia están disponibles para Windows. Skia es también el backend de cromo. Puede cambiar el backend en about: config. Los backends, a su vez, pueden leer todo en la CPU, o pueden usar la aceleración de hardware de la GPU hasta cierto punto. Por ejemplo, Skia tiene su propio backend OpenGL: Ganesh.El código de Direct2D está cerrado, así que será mejor que activemos Skia y veamos qué hace. Se llama a la función para dibujar una curva cúbica SkPath :: cubicTo. Para construir una curva, se divide por el algoritmo de Castelljo en una serie de segmentos rectos, que en realidad se dibujan (ver core / SkGeometry.cpp).Añadir materiales de lectura Código de máquina
Para ser honesto, no logré entender completamente las partes internas de Skia, así que retrocedí un paso - a AppendRoundedRectToPath (), donde todas las operaciones se realizan en enteros, ¿qué podría ser más fácil?Una vez abierto el código desmontado, debemos encontrar la operación de adición en él....
142B1863 00 00 add byte ptr [eax],al
142B1865 00 8D 43 FF 0F 84 add byte ptr [ebp-7BF000BDh],cl
142B186B 67 01 00 add dword ptr [bx+si],eax
142B186E 00 99 0F 57 C9 F7 add byte ptr [ecx-836A8F1h],bl
142B1874 F9 stc
142B1875 8B C3 mov eax,ebx
142B1877 8B CA mov ecx,edx
142B1879 99 cdq
142B187A F7 7C 24 28 idiv eax,dword ptr [esp+28h]
...
Si! Incluso una persona tan lejos de ASM como puedo adivinar fácilmente que la operación ADD es responsable de la adición. Tome la primera operación:142B1863 00 00 add byte ptr [eax],al
0x142B1863 - dirección en RAM0x00 - código de operación - código de instrucción del procesador. Este Mozilla compilado bajo x86, y al abrir la tabla de instrucciones x86 , veremos que el código 00 significa una operación de adición de 8 bits con mnemónicos ADD. El primer operando puede ser un registro o una celda de memoria de acceso aleatorio, el segundo puede ser un registro. El primer operando se agrega al segundo, el resultado se escribe en el primero. Explicaré, por si acaso, que el registro es una memoria RAM ultrarrápida dentro del procesador, por ejemplo, para almacenar resultados de cálculo intermedios.El segundo byte también es 0x00 y se llama MOD-REG-R / M. Sus bits especifican los operandos y el método de direccionamiento.
MOD = 00b en combinación con R / M = 000b significa que se usa direccionamiento indirectoREG = 000b significa que se usa el registro AL (los 8 bits inferiores del registro EAX)[eax] - indica que la adición se realiza con la celda RAM, cuya dirección está en el registro EAX.¿Cómo procesa el procesador el comando ADD?CPU
Basado en la descripción de la microarquitectura Skylake , compilé una lista (extremadamente simplificada) de pasos:- Las instrucciones X86 se obtienen de un caché de instrucciones L1 de 32 KB en un búfer de precodificación de 16 bytes
- Los comandos precodificados se organizan en la Cola de instrucciones (tamaño 2x25) y entran en los decodificadores
- x86 1-4 (µOPs). ADD 1 µOP ALU (- ) 2 µOP AGU ( ) (., ). 86 .
- Allocation Queue (IDQ). , Loop Stream Detector — .
- : , . . .
- La microoperación va al administrador del Programador unificado, quien decide en qué punto y en qué puerto enviar las operaciones para su ejecución fuera de orden de recepción. Detrás de cada puerto hay un actuador. Nuestras micro operaciones irán a ALU y AGU.
El núcleo de SkyLake. Imagen de en.wikichip.org .Repito, esta es mi descripción muy simplificada y no pretende ser precisa y completa. Para mayor referencia, recomiendo leer la publicación Viaje a través del procesador del procesador de computación y el artículo Procesadores de la familia Intel Core i7ALU
Ahora sería interesante saber qué está sucediendo en ALU: ¿cómo se suman los números? Desafortunadamente, la información sobre la implementación específica de microarquitectura y ALU es el secreto comercial de Intel, por lo que pasamos a la teoría más adelante.Un dispositivo para agregar dos bits binarios (es decir, un bit) se llama sumador . La salida es la suma y el bit de acarreo.
Fuente: WikipediaDesde En la vida real, necesitamos agregar números que consisten en varios dígitos, el sumador también debe aceptar el bit de acarreo del dígito anterior como entrada. Tal sumador se llama lleno .
Fuente: WikipediaComo se puede ver en la figura, el sumador está compuesto de elementos lógicos: XOR, AND, OR. Y cada elemento lógicoSe puede implementar utilizando múltiples transistores. O incluso un relevo .
Un ejemplo de implementación de un sumador completo en transistores CMOS . Fuente ¡Entonces llegamos al transistor! Aunque, por supuesto, no solo las ALU funcionan en transistores, sino también en otras unidades de procesador, sino que la mayoría de los transistores se usan en la memoria caché como sus celdas.En realidad, el circuito sumador en nuestro procesador puede construirse de manera diferente y ser mucho más complicado. Por ejemplo, Intel 8008 ya hace 45 años pudo calcular todos los bits de acarreo por adelantado para realizar la suma en paralelo (el llamado sumador con acarreo paralelo). A quién le importa, lea la interesante publicación de blog sobre ingeniería inversa ALU Intel 8008 en el blogKen Shirriff. Es decir Se utilizan varias optimizaciones: por ejemplo, la multiplicación también es beneficiosa para no hacerse "de frente".Conclusiones: ¿qué aprendimos?
- Es complicado
- Se muestra claramente: para resolver el problema de la complejidad excesiva, los ingenieros utilizan la división de sistemas complejos en niveles (capas).
- Las arquitecturas multinivel proporcionan portabilidad: por ejemplo, Firefox puede ejecutarse en varios sistemas operativos y en hardware diferente.
- La interacción entre los niveles se debe a la apertura de las especificaciones para interfaces, servicios y formatos de datos, por ejemplo, HTML y CSS, C ++, un conjunto de comandos x86, etc.
- Nuestro héroe del día está trabajando en la base: un transistor .
PS Soy un aficionado (desarrollador web), y conozco bastante la arquitectura C ++, ASM, BT: desde el curso del instituto, podría estropear algo. Por favor, siéntase libre de enviar comentarios.