Vor 70 Jahren, am 16. Dezember 1947, schufen John Bardin und Walter Brattain in den Bell Labs-Labors unter der Leitung von William Shockley den ersten betriebsbereiten Bipolartransistor. Am 23. Dezember demonstrierte Brattain seinen Kollegen den ersten Transistorverstärker. Daher wird dieser Tag oft als Transistortag bezeichnet .
Es besteht keine Notwendigkeit, über die Bedeutung dieses Ereignisses zu sprechen. Der Transistor gilt als eine der wichtigsten Erfindungen des 20. Jahrhunderts, ohne die Computer noch an Lampen und Relais arbeiten und ganze Gebäude einnehmen würden. Shockley, Bardin und Brattain erhielten 1956 für ihre Arbeit den Nobelpreis für Physik. Im Laufe der Jahre hat sich der Transistor auf wenige Atome miniaturisiert. Jeder Prozessor hat Milliarden von Transistoren, so dass der Transistor als das massereichste Gerät bezeichnet werden kann, das von der Menschheit geschaffen wurde.Aber welche Art von Arbeit leistet der Transistor für uns? Machen wir eine mentale Reise: Wir werden den Weg von einigen hochrangigen Fingerspitzen bis zu unserem Geburtstag verfolgen - dem Transistor.Was ist als Ausgangspunkt zu nehmen? Nun, ziehe wenigstens einen Habrakat-Knopf.HTML und CSS
Eine Schaltfläche besteht aus Hintergrundpixeln, Text und Rahmen. Im Code, der durch das <a> -Tag festgelegt wird, auf das CSS-Layoutregeln angewendet werden. Beispielsweise wird eine CSS-Regel auf einen Rand für runde Ecken angewendet:border-radius: 3px;
Somit besteht die Grenze aus vier Segmenten und vier Bögen ("Viertel" eines Kreises).Browser
Für die Recherche habe ich meinen Lieblings-Firefox genommen. Bevor FF mit dem Zeichnen unseres Buttons beginnt, muss er viel an der Analyse und Berechnung der Position von Elementen arbeiten:- Laden Sie HTML über ein Netzwerk herunter, analysieren Sie es und erstellen Sie einen DOM-Baum
- Download über das CSS-Netzwerk, lexikalische Analyse durchführen, analysieren
- Binden Sie Regeln basierend auf Priorität und Vererbung an Seitenelemente
- Erstellen Sie für alle sichtbaren DOM-Knoten einen Baum ihrer rechteckigen Bereiche - Rahmen.
- Berechnen Sie für Frames die Abmessungen und den Standort (siehe Video ).
- Erstellen Sie Ebenen aus Frames unter Berücksichtigung des Z-Index und des Inhaltstyps (<canvas>, SVG, <video>).
- Erstellen Sie eine Zeichnungsliste in der Reihenfolge: Hintergrundfarbe, Hintergrundbild, Rahmen, Nachkommen, Umriss.
Hinzufügen. Lesematerial: Wir werden nicht im Detail auf diese Schritte eingehen. Danach folgt die eigentliche Zeichnung der notwendigen Elemente.Laden Sie die Quelle herunter, um herauszufinden, was dort passiert.Mozilla Firefox.
Firefox Mercurial Visual Studio C++. VS
symbols.mozilla.org. - /layout/.
, , , Firefox. c , , — FF.
Die Datei nsCSSRenderingBorders.cpp ist für das Zeichnen von Rahmen verantwortlich . Und die allgemeine Funktion des Zeichnens von Rändern heißt (wer hätte das gedacht): DrawBorders () . Die Funktion wählt die optimale Rendermethode für verschiedene Situationen aus. Wir haben einen relativ einfachen Fall: Es gibt einen Randradius, aber die Ränder auf allen Seiten sind durchgehend und von derselben Farbe.Unser wennif (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;
}
Es gibt viel komplexere Optionen, z. B. das Andocken in Ecken mit Randradius verschiedener Arten von gepunkteten und gestrichelten Rändern, siehe DrawDashedOrDottedCorner () . Dort im Code komplettAber zurück zu unserem Wenn. Aus dem Kommentar erfahren wir, dass in diesem Fall der Rand mit zwei Rechtecken gezeichnet wird - intern und extern, dann wird der erstellte Pfad (Pfad) mit der gewünschten Farbe gefüllt.AppendRoundedRectToPath(builder, mOuterRect, mBorderRadii, true);
AppendRoundedRectToPath(builder, ToRect(borderInnerRect.rect), borderInnerRect.corners, false);
RefPtr<Path> path = builder->Finish();
mDrawTarget->Fill(path, color);
Gehen Sie zu AppendRoundedRectToPath () in gfx / 2d / PathHelpers.cpp.Wieder setzen wir Haltepunkte Wir lernen aus dem Kommentar zur Funktion, dass die Ecken an vier Kontrollpunkten durch Bezier-Kurven gezeichnet werden . Bezier-Kurven werden häufig in Computergrafiken verwendet, unter anderem zum Zeichnen von Kreisbögen und Ellipsen. Wie wir weiter aus dem Kommentar erfahren, gibt es viele Möglichkeiten, Kontrollpunkte für die Erstellung einer Kurve auszuwählen. In diesem Fall müssen die Punkte 0 und 3 zu den Seiten des Rechtecks gehören, die Punkte 0, 1 und C liegen auf einer geraden Linie, die Punkte 3, 2 und C auf der anderen. Siehe Abbildung:
Es bleibt uns überlassen, das Verhältnis der Längen der Segmente 01 / 0C und 32 / 3C zu berechnen. Hier verwenden die Autoren ungefähre Berechnungen und erhalten die magische Konstante Alpha:const Float alpha = Float(0.55191497064665766025);
Leider sind Artikel mit dem Checkpoint-Auswahlalgorithmus, auf den der Kommentar verweist, nicht gemeinfrei. Im Allgemeinen sollte jedoch beachtet werden, dass Algorithmen in der Computergrafik häufig eine Approximation verwenden, um die Leistung zu verbessern. Mit dem Brezenham-Algorithmus können Sie beispielsweise Segmente und Kreise nicht „in der Stirn“ zeichnen - indem Sie die Gleichungen y = f (x) lösen, sondern mit schlaueren ganzzahligen Operationen. Gleiches gilt für Füllung usw.Weiter im Zyklus gehen wir von Ecke zu Ecke, verwenden Alpha, um die Koordinaten der Kontrollpunkte zu berechnen, und rufen schließlich die Funktionen zum Zeichnen der Grenzlinie und des Eckbogens auf:aPathBuilder->LineTo(p0);
aPathBuilder->BezierTo(p1, p2, p3);
Vollständiger AppendRoundedRectToPath () Codevoid
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();
}
Aber dann hängt alles vom Backend der 2D-Grafiken ab, die Mozilla verwendet.Grafik-Engine
Gecko verwendet die plattformunabhängige Moz2D-Bibliothek, die wiederum eines der Backends verwenden kann: Cairo, Skia, Direct2D, Quartz und NV Path. Zum Beispiel sind Direct2D, Cairo, Skia für Windows verfügbar. Skia ist auch das Chromium-Backend. Sie können das Backend in about: config ändern. Die Backends können wiederum alles auf der CPU lesen oder die GPU-Hardwarebeschleunigung in gewissem Umfang nutzen. Zum Beispiel hat Skia ein eigenes OpenGL-Backend - Ganesh.Direct2D-Code ist geschlossen, daher sollten wir Skia besser einschalten und sehen, was es tut. Die Funktion zum Zeichnen einer kubischen Kurve SkPath :: cubicTo wird aufgerufen. Um eine Kurve zu erstellen , wird sie vom de Castelljo-Algorithmus in mehrere gerade Segmente unterteilt, die tatsächlich gezeichnet werden (siehe core / SkGeometry.cpp).Maschinencode
Um ehrlich zu sein, habe ich es nicht geschafft, die Skia-Interna vollständig zu verstehen, also bin ich einen Schritt zurückgegangen - zu AppendRoundedRectToPath (), wo alle Operationen mit ganzen Zahlen ausgeführt werden - was könnte einfacher sein?Nachdem wir den zerlegten Code geöffnet haben, müssen wir die Additionsoperation darin finden....
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]
...
Ja! Sogar eine Person, die so weit von ASM entfernt ist, wie ich leicht erraten kann, dass die ADD-Operation für die Hinzufügung verantwortlich ist. Nehmen Sie die erste Operation vor:142B1863 00 00 add byte ptr [eax],al
0x142B1863 - Adresse im RAM0x00 - Opcode - Prozessorbefehlscode. Dieser unter x86 kompilierte Mozilla und das Öffnen der x86-Befehlstabelle werden sehen, dass Code 00 eine 8-Bit-Additionsoperation mit ADD-Mnemonik bedeutet. Der erste Operand kann ein Register oder eine Direktzugriffsspeicherzelle sein, der zweite kann ein Register sein. Der erste Operand wird zum zweiten hinzugefügt, das Ergebnis wird zum ersten geschrieben. Ich werde nur für den Fall erklären, dass das Register ein ultraschneller RAM-Speicher im Prozessor ist, zum Beispiel zum Speichern von Zwischenberechnungsergebnissen.Das zweite Byte ist ebenfalls 0x00 und heißt MOD-REG-R / M.. Seine Bits geben die Operanden und die Adressierungsmethode an.
MOD = 00b in Kombination mit R / M = 000b bedeutet, dass indirekte Adressierung verwendet wird.REG = 000b bedeutet, dass das AL-Register verwendet wird (die unteren 8 Bits des EAX-Registers)[eax] - zeigt an, dass die Addition mit der RAM-Zelle erfolgt. deren Adresse befindet sich im EAX-Register.Wie verarbeitet der Prozessor den ADD-Befehl?CPU
Basierend auf der Beschreibung der Skylake- Mikroarchitektur habe ich eine (extrem vereinfachte) Liste von Schritten zusammengestellt:- X86-Befehle werden aus einem 32-KB-L1-Befehls-Cache in einen 16-Byte-Vorcodierungspuffer abgerufen
- Die vorcodierten Befehle sind in der Anweisungswarteschlange (2 x 25) organisiert und gelangen in die Decoder
- x86 1-4 (µOPs). ADD 1 µOP ALU (- ) 2 µOP AGU ( ) (., ). 86 .
- Allocation Queue (IDQ). , Loop Stream Detector — .
- : , . . .
- Die Mikrooperation geht an den Unified Scheduler-Manager, der entscheidet, an welchem Punkt und an welchem Port Operationen zur Ausführung außerhalb der Empfangsreihenfolge gesendet werden sollen. Hinter jedem Anschluss befindet sich ein Aktuator. Unsere Mikrooperationen werden an ALU und AGU gehen.
Der Kern von SkyLake. Bild von en.wikichip.org .Ich wiederhole, dies ist meine sehr vereinfachte Beschreibung und gibt nicht vor, genau und vollständig zu sein. Als weitere Referenz empfehle ich, den Beitrag Reise durch den Computerprozessor-Prozessor und den Artikel Prozessoren aus der Intel Core i7-Familie zu lesenALU
Jetzt wäre es interessant zu wissen, was in ALU passiert: Wie addieren sich die Zahlen? Leider sind Informationen zur spezifischen Implementierung von Mikroarchitektur und ALU Intels Geschäftsgeheimnis, daher wenden wir uns später der Theorie zu.Eine Vorrichtung zum Hinzufügen von zwei Binärbits (d. H. Einem Bit) wird als Addierer bezeichnet . Die Ausgabe ist das Summen- und Übertragsbit.
Quelle: WikipediaSeit Im wirklichen Leben müssen wir Zahlen hinzufügen, die aus mehreren Ziffern bestehen. Der Addierer muss auch das Übertragsbit der vorherigen Ziffer als Eingabe akzeptieren. Ein solcher Addierer heißt voll .
Quelle: WikipediaWie aus der Abbildung ersichtlich, besteht der Addierer aus logischen Elementen: XOR, AND, OR. Und jedes logische Elementkann mit mehreren Transistoren implementiert werden. Oder sogar ein Relais .
Eine beispielhafte Implementierung eines Volladdierers auf CMOS- Transistoren. QuelleAlso sind wir zum Transistor gekommen! Natürlich arbeiten nicht nur ALUs mit Transistoren, sondern auch mit anderen Prozessoreinheiten, aber die meisten Transistoren werden im Cache als Zellen verwendet.In der Realität kann die Addiererschaltung in unserem Prozessor anders aufgebaut und viel komplizierter sein. Zum Beispiel konnte Intel 8008 bereits vor 45 Jahren alle Übertragsbits im Voraus berechnen, um eine Addition parallel durchzuführen (der sogenannte Addierer mit parallelem Übertrag). Wen kümmert es, lesen Sie den interessanten Blog- Beitrag über Reverse Engineering ALU Intel 8008 im BlogKen Shirriff. Das heißt, Es werden verschiedene Optimierungen verwendet: Zum Beispiel ist die Multiplikation auch vorteilhaft, wenn sie nicht „frontal“ durchgeführt wird.Schlussfolgerungen: Was haben wir gelernt?
- Alles ist schwierig
- Es wird deutlich gezeigt: Um das Problem der übermäßigen Komplexität zu lösen, verwenden Ingenieure die Aufteilung komplexer Systeme in Ebenen (Ebenen).
- Mehrstufige Architekturen bieten Portabilität: Firefox kann beispielsweise auf verschiedenen Betriebssystemen und auf unterschiedlicher Hardware ausgeführt werden.
- Die Interaktion zwischen den Ebenen beruht auf der Offenheit der Spezifikationen für Schnittstellen, Dienste und Datenformate, z. B. HTML und CSS, C ++, eine Reihe von x86-Befehlen usw.
- Unser Held des Tages arbeitet ganz unten - ein Transistor .
PS Ich bin ein Amateur (Webentwickler) und kenne die C ++ -, ASM- und BT-Architektur ziemlich gut - vom Institutskurs aus könnte ich etwas durcheinander bringen. Bitte zögern Sie nicht, Kommentare zu senden.