Lowlevel
Lowlevel => Lowlevel-Coding => Thema gestartet von: OdinX am 06. November 2015, 16:27
-
Hallo zusammen
Ich bin gerade daran, das Tutorial https://littleosbook.github.io/ (https://littleosbook.github.io/) durchzuarbeiten. Das hat gut geklappt, ich konnte den Framebuffer und den Serial Port benutzen. Dann habe ich die GDT initialisiert, da weiss ich aber nicht, ob es funktioniert hat, keine Ahnung an was man das sehen soll. Auf jeden Fall wurden meine Framebuffer ausgaben nach dem LGDT immer noch ausgeführt. Dann habe ich die IDT und die PICs initialisiert und die Interrupthandler gebaut, und da passiert mal einfach rein gar nichts. Die interrupt_handler Funktion sollte etwas in den Framebuffer schreiben, um zu zeigen, dass sie aufgerufen wird, das passiert aber nicht. Bin jetzt wirklich langsam ratlos. Aber am besten zeige ich mal ein bisschen Code.
kmain.c
#include "segmentation.h"
#include "interrupts.h"
#include "pic.h"
void kmain() {
initializeGDT();
initializeIDT();
initializePIC();
}
segmentation.c
#include "segmentation.h"
#include "io.h"
struct gdt_entry createGDTEntry(unsigned int, unsigned int, char, char);
struct gdt_entry {
unsigned short limit_low;
unsigned short base_low;
unsigned char base_mid;
unsigned char access_flags;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));
struct gdt_entry gdt[3];
#define EXECUTABLE 0x8
#define READABLE 0x2
void initializeGDT() {
gdt[0] = createGDTEntry(0, 0, 0, 0);
gdt[1] = createGDTEntry(0, ~0, READABLE, 0);
gdt[2] = createGDTEntry(0, ~0, READABLE | EXECUTABLE, 0);
lgdt(gdt);
}
#define GRANULARITY_4KB 0x80
#define IS_32_BIT_CODE 0x40
#define PRIVILEGE(lvl) ((lvl & 2) << 5)
#define PRESENT 0x80
struct gdt_entry createGDTEntry(unsigned int base, unsigned int limit, char type, char pl) {
struct gdt_entry result;
result.base_low = base & 0xffff;
result.base_mid = (base >> 16) & 0xff;
result.base_high = (base >> 24) & 0xff;
result.limit_low = limit & 0xff;
result.granularity = GRANULARITY_4KB | IS_32_BIT_CODE | ((limit >> 16) & 0xf);
result.access_flags = PRESENT | PRIVILEGE(pl) | type;
return result;
}
interrupts.c
#include "interrupts.h"
#include "io.h"
#include "framebuffer.h"
#include "interrupt_handler.h"
void handler();
void createIDTEntry();
struct idt_entry {
unsigned short offset_low;
unsigned short selector;
unsigned char unused; //but must be zeroed
unsigned char flags;
unsigned short offset_high;
} __attribute__((packed));
struct idt_ptr {
unsigned short limit;
unsigned int base;
} __attribute__((packed));
struct idt_entry idt[256];
void initializeIDT() {
int i;
for (i = 0; i < 256; i++) {
createIDTEntry(i, &interrupt_handler_1);
}
struct idt_ptr ptr;
ptr.limit = (sizeof (struct idt_entry) * 256) - 1;
ptr.base = (unsigned int) &idt;
lidt(&ptr);
}
void interrupt_handler () {
fb_write("interrupt happend!", 18);
fb_newLine();
}
#define TRAP_GATE_32BIT 0xf
#define PRIVILEGE(lvl) ((lvl & 2) << 5)
#define SEGSEL_KERNEL_CS 0x08
void createIDTEntry(int n, int address) {
idt[n].offset_low = address & 0xffff;
idt[n].offset_high = (address >> 16) & 0xffff;
idt[n].flags = TRAP_GATE_32BIT | PRIVILEGE(4);
idt[n].selector = SEGSEL_KERNEL_CS;
idt[n].unused = 0;
}
interrupt_handler.s
extern interrupt_handler
global enable_interrupts
global disable_interrupts
%macro no_error_handler 1
global interrupt_handler_%1
interrupt_handler_%1:
push dword 0
push dword %1
jmp common_interrupt_handler
%endmacro
%macro error_code_handler 1
global interrupt_handler_%1
interrupt_handler_%1:
push dword %1
jmp common_interrupt_handler
%endmacro
section .text:
common_interrupt_handler:
push esp
add dword [esp], 8
push eax
push ebx
push ecx
push edx
push ebp
push esi
push edi
call interrupt_handler
pop edi
pop esi
pop ebp
pop edx
pop ecx
pop ebx
pop eax
pop esp
iret
enable_interrupts:
sti
ret
disable_interrupts:
cli
ret
no_error_handler 0
no_error_handler 1
no_error_handler 2
no_error_handler 3
no_error_handler 4
no_error_handler 5
no_error_handler 6
no_error_handler 7
error_code_handler 8
no_error_handler 9
error_code_handler 10
error_code_handler 11
error_code_handler 12
error_code_handler 13
error_code_handler 14
no_error_handler 15
no_error_handler 16
error_code_handler 17
no_error_handler 18
no_error_handler 19
no_error_handler 32
no_error_handler 33
no_error_handler 34
no_error_handler 35
no_error_handler 36
no_error_handler 37
no_error_handler 38
no_error_handler 39
no_error_handler 40
no_error_handler 41
no_error_handler 42
no_error_handler 43
no_error_handler 44
no_error_handler 45
no_error_handler 46
no_error_handler 47
pic.c
#include "pic.h"
#include "io.h"
void pic_mask(unsigned int, unsigned int);
#define PIC1_PORT_A 0x20
#define PIC1_PORT_B 0x21
#define PIC1_ICW1 0x11
#define PIC1_ICW2 0x20
#define PIC1_ICW3 0x4
#define PIC1_ICW4 0x5
#define PIC2_PORT_A 0xa0
#define PIC2_PORT_B 0xa1
#define PIC2_ICW1 0x11
#define PIC2_ICW2 0x28
#define PIC2_ICW3 0x2
#define PIC2_ICW4 0x1
#define PIC_EOI 0x20
void initializePIC() {
outb(PIC1_PORT_A, PIC1_ICW1);
outb(PIC2_PORT_A, PIC2_ICW1);
outb(PIC1_PORT_B, PIC1_ICW2);
outb(PIC2_PORT_B, PIC2_ICW2);
outb(PIC1_PORT_B, PIC1_ICW3);
outb(PIC2_PORT_B, PIC2_ICW3);
outb(PIC1_PORT_B, PIC1_ICW4);
outb(PIC2_PORT_B, PIC2_ICW4);
pic_mask(0xec, 0xff);
}
void pic_acknowledge() {
outb(PIC1_PORT_A, PIC_EOI);
outb(PIC2_PORT_A, PIC_EOI);
}
void pic_mask(unsigned int mask1, unsigned int mask2) {
outb(PIC1_PORT_B, mask1);
outb(PIC2_PORT_B, mask2);
}
io.s
#include "pic.h"
#include "io.h"
void pic_mask(unsigned int, unsigned int);
#define PIC1_PORT_A 0x20
#define PIC1_PORT_B 0x21
#define PIC1_ICW1 0x11
#define PIC1_ICW2 0x20
#define PIC1_ICW3 0x4
#define PIC1_ICW4 0x5
#define PIC2_PORT_A 0xa0
#define PIC2_PORT_B 0xa1
#define PIC2_ICW1 0x11
#define PIC2_ICW2 0x28
#define PIC2_ICW3 0x2
#define PIC2_ICW4 0x1
#define PIC_EOI 0x20
void initializePIC() {
outb(PIC1_PORT_A, PIC1_ICW1);
outb(PIC2_PORT_A, PIC2_ICW1);
outb(PIC1_PORT_B, PIC1_ICW2);
outb(PIC2_PORT_B, PIC2_ICW2);
outb(PIC1_PORT_B, PIC1_ICW3);
outb(PIC2_PORT_B, PIC2_ICW3);
outb(PIC1_PORT_B, PIC1_ICW4);
outb(PIC2_PORT_B, PIC2_ICW4);
pic_mask(0xec, 0xff);
}
void pic_acknowledge() {
outb(PIC1_PORT_A, PIC_EOI);
outb(PIC2_PORT_A, PIC_EOI);
}
void pic_mask(unsigned int mask1, unsigned int mask2) {
outb(PIC1_PORT_B, mask1);
outb(PIC2_PORT_B, mask2);
}
header files und so habe ich mal weggelassen, ich denke das ist jetzt schon viel zu viel code für so eine frage.
Warum wird die interrupt_handler funktion in interrupts.c nicht aufgerufen, wenn ein interrupt passiert, also zB ich eine Taste drücke?
Danke schon mal für alle, die so weit gelesen haben.
-
Hallo OdinX,
ich habe deinen Code kurz überflogen, dabei sind mir zwei Dinge aufgefallen: zum einen sieht es so aus, dass enable_interrupts nie aufgerufen wird (vielleicht hast du die entsprechende Code-Stelle aber auch einfach nicht gezeigt) und dann scheinst du hier
for (i = 0; i < 256; i++) {
createIDTEntry(i, &interrupt_handler_1);
}
für jeden Interrupt den interrupt_handler_1 einzutragen, ist das so gewünscht? Und wenn ja, werden Hardware Interrupts auch richtig behandelt (senden das End of Interrupt), sodass du zum Beispsiel auch immer weitere Timer-Interrupts erhältst?
Das erstellen der Einträge für GDT und IDT habe ich mir jetzt nicht genauer angesehen, sieht aber auf den ersten Blick gut aus.
Ich hoffe das hilft dir etwas weiter.
-
Hallo OdinX,
ich vermute einer der Fehler liegt an dieser Stelle:
for (i = 0; i < 256; i++) {
createIDTEntry(i, &interrupt_handler_1);
}
Du musst nämlich die Addresse vom Assembler Interrupthandler hineinschreiben und nicht den C-Handler das kann böse enden.
Oh, da gab es wohl jemand, der schneller war als ich :-o.
-
Woah... das ging ja schnell mit den Antworten.
Das mit dem Interrupt interrupt_handler_1 der allen zugeteilt wird habe ich extra gemacht, weil ich erst testen wollte ob es funktioniert, und erst danach die Zuteilung fixen. Ich denke das war aber eine dumme Idee, da ja die einen Interrupt Handler Parameter auf dem Stack übergeben bekommen, und die anderen nicht.
Ich hab jetzt mal geschaut, ob ich die funktion enable_interrupts aufgerufen habe, und das war wohl mein grösstes problem... so trivial kann es sein. Nun rebootet bochs aber sofort, nachdem ganz kurz mein restlicher output angezeigt wird. Mal schauen ob ich diesmal rausfinde, was es sein könnte.
Danke für eure schnelle Hilfe.
-
Ich hab mal alles geändert, was so von den vorherigen Posts angestanden ist. Nun rebootet bochs dauernd, wie bereits gesagt. Ich kriege folgende Meldung im log von bochs:
01187800207e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x20)
01187800207e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x0d)
01187800207e[CPU0 ] interrupt(): gate descriptor is not valid sys seg (vector=0x08)
Müsste gate descriptor 0x20 nicht valid sein, wenn ich so was in meiner initializeIDT funktion habe:
#define CREATE_IDT_GATE(i) createIDTEntry(i, &interrupt_handler_##i)
void initializeIDT() {
...
CREATE_IDT_GATE(32);
...
}
-
Ich hab jetzt mal versucht mich etwas genauer mit deinem Code zu befassen (mit dem Erstellen der Einträge für GDT und IDT), dabei ist mir aufgefallen, dass du für die IDT Einträge den Selector 0x8 verwendest, der sich auf den Eintrag 1 in der GDT beziehen müsste:
gdt[1] = createGDTEntry(0, ~0, READABLE, 0);
Für ein Codesegment fehlt da denke ich das EXECUTABLE Flag.
-
Um Probleme mit der GDT zu finden, solltest du gleich nach dem lgdt alle Segmentregister neu laden (bei den Datensegmenten geht das direkt, für cs musst du einen ljmp machen).
-
Ich habe das mal so angepasst:
in segmentation.h
void initializeGDT() {
gdt[0] = createGDTEntry(0, 0, 0, 0);
gdt[1] = createGDTEntry(0, ~0, READABLE | EXECUTABLE, 0);
gdt[2] = createGDTEntry(0, ~0, READABLE, 0);
lgdt(&gdt);
}
und in io.s
lgdt:
mov eax, [esp + 4]
lgdt [eax]
jmp SEGSEL_KERNEL_CS:.load_segments
.load_segments
mov ax, SEGSEL_KERNEL_DS
mov ds, ax
mov ss, ax
mov es, ax
mov gs, ax
mov fs, ax
ret
Rebootet aber immer noch dauernd.
Wie kann ich das debuggen? Den bochs debugger hab ich bis jetzt auf jeden Fall noch nicht zum laufen bekommen
edit: ich hab jetzt den debugger hinbekommen, aber bin noch nicht dazu gekommen, weiter zu machen.
-
Ich habe noch einen Fehler in deinem Code gefunden: Beim Laden der GDT - Die Assembler Anweisung lgdt erwartet einen GDT Descriptor, nicht die GDT Selber.
Der Descriptor hat folgenden Aufbau
struct SGDTDescriptor {
uint16_t size; //die Größe der GDT in Bytes - 1
uint32_t offset; //die lineare Startaddresse der GDT
} __attribute__ ((packed));
-
I am an idiot... Should have been super obvious that it must be a struct for the gdt, when I had to make a struct for the idt.
I'll try making that work this week.
Thank you very much for your help.
-
Freut mich, dass ich helfen konnte. :-)
-
Hey, schon eine weile her seit ich an meinem kleinen OS gearbeitet habe, und hab das darum noch nicht zum laufen gebracht. Hab heute wieder angefangen und wollte mal das ganze ohne IDT, also nur mit GDT testen, schein aber immer noch nicht zu funktionieren. Ich poste mal den relevanten Code.
segmentation.c
#include "segmentation.h"
#include "io.h"
struct gdt_entry {
unsigned short limit_low;
unsigned short base_low;
unsigned char base_mid;
unsigned char access_flags;
unsigned char granularity;
unsigned char base_high;
} __attribute__((packed));
struct gdt_ptr {
unsigned short size;
unsigned int offset;
} __attribute__((packed));
#define GRANULARITY_4KB (1 << 7)
#define IS_32_BIT_CODE (1 << 6)
#define PRIVILEGE(lvl) ((lvl & 3) << 6)
#define PRESENT (1 << 7)
#define GDT_NUM_ENTRIES 3
struct gdt_entry gdt[GDT_NUM_ENTRIES];
void createGDTEntry(int n, unsigned int base, unsigned int limit, char type, char pl) {
gdt[n].base_low = base & 0xffff;
gdt[n].base_mid = (base >> 16) & 0xff;
gdt[n].base_high = (base >> 24) & 0xff;
gdt[n].limit_low = limit & 0xffff;
gdt[n].granularity = GRANULARITY_4KB | IS_32_BIT_CODE | ((limit >> 16) & 0xf);
gdt[n].access_flags = PRESENT | PRIVILEGE(pl) | (1 << 4) | (type & 0xf);
}
#define RX 0xa
#define RW 0x2
void initializeGDT() {
createGDTEntry(0, 0, 0, 0 , 0);
createGDTEntry(1, 0, ~0, RX, 0);
createGDTEntry(2, 0, ~0, RW, 0);
struct gdt_ptr ptr;
ptr.size = sizeof(struct gdt_entry) * GDT_NUM_ENTRIES - 1;
ptr.offset = (int) &gdt;
lgdt(ptr);
}
io.s
lgdt:
mov eax, [esp + 4]
lgdt [eax]
jmp SEGSEL_KERNEL_CS:.load_segments
.load_segments
mov ax, SEGSEL_KERNEL_DS
mov ds, ax
mov ss, ax
mov es, ax
mov gs, ax
mov fs, ax
ret
Ich hoffe ich hab diesmal nichts relevanes vergessen.
:-o ich bin ja beim letzten mal plötzlich auf englisch umgestiegen in meinem Kommentar, hatte ich bisher gar nicht gemerkt.
edit:
bzw noch die wichtigere Frage wäre eigentlich: wie debuggt man so etwas? Wie weiss ich, dass die GDT geladen ist? Momentan weiss ich, dass es nicht geht, weil es rebootet, aber wenn es nun nicht rebooten würde wüsste ich ja trotzdem nicht, ob es funktioniert hat.
-
Nur Kleinkram, und vermutlich auch nicht die Lösung:
- Gibt es einen Grund, dass du die GDT in Assembler lädst?
Im hauseigenen Tutorial (http://www.lowlevel.eu/wiki/OS-Dev_f%C3%BCr_Einsteiger) gibt es dafür wunderbar funktionierenden C-Code. ;-) - Für Strukturen fixer Größe solltest du immer(!) auch Datentypen fixer Größe nehmen.
Also in struct gdt_entry sollten uint16_t, uint8_t und so auftauchen. - Bitfelder sind zwar nicht portabel, aber für solche (ohnehin nicht portablen) Datenstrukturen auch eine gute Idee.
Also eine Union aus dem Bitfeld und einem uint64_t (oder char[8]). - In deiner struct gdt_ptr ist der offset ein unsigned int, die Adresse wird aber als int da reingecastet.
- Solltest du der Funktion lgdt() nicht eher die Adresse von ptr mitteilen? Stimmt die ABI für den Aufruf?
- Nachtrag: Deine io.s sollte io.S heißen, sonst kannst du Probleme mit dem Präprozessor bekommen (Windows ist nicht case-sensitive, aber immerhin case-preserving; falls du den Code jemals auf ein Linux kopierst oder so.)
Im Qemu-Monitor kannst du dir GDTR, IDTR und die Segmentregister anzeigen lassen (info registers) und prüfen, ob die Werte auch stimmen. Außerdem kann Qemu noch ein Log schreiben (qemu-system-i386 -d int). Außerdem kannst du GDB zum Debuggen benutzen, aber das habe ich nur für Breakpoints zwischen Instruktionen genutzt (zwischen "lade CR3" und "lade die nächste Instruktion"; im Monitor konnte man sich dann die kaputten Paging-Strukturen anschauen, bevor es knallte). Ist auch schon länger her.
Ansonsten schaffe ich mir grundsätzlich möglichst früh eine Debugausgabe, also ein rudimentäres printf(), welches direkt auf Bildschirm oder UART schreibt. Das geht auch schon, bevor die GDT steht. ;-)
-
Bitfelder sind zwar nicht portabel, aber für solche (ohnehin nicht portablen) Datenstrukturen auch eine gute Idee.
Also eine Union aus dem Bitfeld und einem uint64_t (oder char[8]).
Du wirfst hier Portabilität zwischen Zielarchitekturen und Portabilität zwischen Buildumgebungen durcheinander. Bitfelder sind nie eine gute Idee, wenn du eine bestimmte Anordnung brauchst, weil die Anordnung schlicht undefiniert ist.
Solltest du der Funktion lgdt() nicht eher die Adresse von ptr mitteilen? Stimmt die ABI für den Aufruf?
Das ist auch das enizige ernsthafte Problem, das mir auffällt.
-
Ich wollte die GDT in Assembler laden, weil das so im Tutorial erklärt wurde. Ich benutze das Tutorial https://littleosbook.github.io/ (https://littleosbook.github.io/).
Im von dir verlinkten Tutorial finde ich aber nur dass die Funktionen set_entry und load_gdt aufgerufen werden, aber die sind nirgendwo definiert, oder zu mindest habe ich die nicht gefunden.
Die Datentypen werde ich dann noch ändern, ist ja nur wein wenig stupide Fleissarbeit.
Von welchem Bitfeld redest du? Die gdt_entry struct? Und wenn ja, was bringen mir da unions? Ich weiss eigentlich schon wie unions funktionieren, ich seh nur den Anwendungszweck in dem Zusammenhang gerade nicht.
Das mit dem cast von int zu unsigned int ist zwar unschön, sollte aber keinen Unterschied machen, da das Bitmuster bei diesem cast nicht verändert wird.
Sieht aus als sei das das Problem, dass ich nicht die Adresse übergebe, hoffentlich funktioniert es dann.
Ich benutze im Übrigen eine Ubuntu vm um das ganze zu programmieren, und ich habe noch keine Probleme mit dem kleinen .s. Warum würde das denn zu Problemen kommen? Gibt es da build tools die die entsprechende .S Endung voraussetzen?
Danke viel mal für eure schnelle Hilfe, ich seid wirklich sehr schnell und lehrreich.
-
Ich benutze im Übrigen eine Ubuntu vm um das ganze zu programmieren, und ich habe noch keine Probleme mit dem kleinen .s. Warum würde das denn zu Problemen kommen? Gibt es da build tools die die entsprechende .S Endung voraussetzen?
Also eine .S-Endung bedeuted, dass dies eine Assemblerdatei, die noch durch den Präprozessiert werden muss. Als Ergebnis entsteht dann eine .s Assemblerdatei und diese wird dann direkt assembliert.
So hab ich das auf jeden Fall mal gelernt. Kann natürlich auch falsch sein. Schau einfach mal das Ergebnis mit objdump an, dann siehst du, ob deine Datei richtig assembliert wurde.
-
Ach so. Das würde dann also heissen, dass ich möglicherweise nur darum keine Probleme bekommen habe mit dem .s, weil ich bisher keinen Präprozessor für Assembler benutzt hatte, und dementsprechend würde ich da später in den Hammer laufen.
Ich habe das so gemacht, weil es im Tutorial so gemacht wurde. Ist aber gut zu wissen, da ich mir damit viel Fehlersuche ersparen kann.
-
Ich habe das gerade mal so geändert, was da so anstand und es scheint jetzt zu funktionieren. Na dann gehts für mich mal weiter zu den Interrupts :).
Ich danke euch viel mal für eure Hilfe!
Ich hätte da aber noch eine theoretische Frage.
Warum kann man Paging nicht ohne Segmentation nutzen? Bzw welchen Protection Mechanismus wird vom Paging nicht zur verfügung gestellt, den Segmentation bietet? Ich hatte als, ich die Theorie über Paging und Segmentation gelernt habe, immer das Gefühl, dass Paging quasi Segmentation ersetzt. Scheint aber nicht der Fall zu sein, denn man muss Segmentation trotzdem noch konfigurieren.
-
Man kann nicht nur Paging, sondern allgemein keine x86-Prozessoren ohne Segmentierung benutzen. Ist einfach nicht abschaltbar, auch wenn man meistens nichts sinnvolles damit anstellt, wenn man Paging hat.
Bei x86_64 ist die Segmentierung noch ein bisschen verkrüppelt worden, indem für die meisten Segmentregister Basis 0 und volles Limit angenommen wird, d.h. selbst wenn man wollte, könnte man mit Segmentierung nicht mehr viel anfangen, aber konfigurieren muss man es immer noch. fs und gs berücksichtigen die Basis noch, deswegen werden die gerne für Sachen wie TLS benutzt.
-
Super, danke, das hilft mir für das Verständnis.
-
Bitfelder sind nie eine gute Idee, wenn du eine bestimmte Anordnung brauchst, weil die Anordnung schlicht undefiniert ist.
Hm. Ich weiß, dass Bitfelder für Registerbeschreibungen eingesetzt werden, weil es den Code lesbar halten kann und einem das manuelle Ausmaskieren (und damit Fehler) erspart. Ist natürlich alles implementation-defined, aber das wäre hier ja eher egal. In C++ kann man Bitfelder auch portabel als Klasse nachbilden und bekommt dann typsichere Registerbeschreibungen.
Im von dir verlinkten Tutorial finde ich aber nur dass die Funktionen set_entry und load_gdt aufgerufen werden, aber die sind nirgendwo definiert, oder zu mindest habe ich die nicht gefunden.
Den Code dazu findest du im Artikel zur GDT (http://www.lowlevel.eu/wiki/Global_Descriptor_Table).
Von welchem Bitfeld redest du? Die gdt_entry struct? Und wenn ja, was bringen mir da unions? Ich weiss eigentlich schon wie unions funktionieren, ich seh nur den Anwendungszweck in dem Zusammenhang gerade nicht.
Das ist ein sehr breiter Weg ins Land von implementation-defined und daher nur vorsichtig zu genießen (ungetestet):
union gdt_entry {
struct {
unsigned limit_low : 16;
unsigned base_low : 24;
unsigned type : 4;
unsigned segment : 1;
unsigned dpl : 1;
unsigned present : 1;
unsigned limit_high : 4;
unsigned available : 1;
unsigned longmode : 1;
unsigned size : 1;
unsigned granularity : 1;
unsigned base_high : 8;
} PACKED;
char raw[8];
};
Damit kannst du jedes Teilfeld des Registers einzeln setzen und lesen, wobei der Compiler das Maskieren und Shiften für dich erledigt; das gesamte Register findest du dann, wenn du "raw" ausliest. Achtung: Bitfelder sind pfui. In ein Element einer Union was reinschreiben und aus einem anderen Element etwas rauslesen ist auch pfui. Da musst du aufpassen, was dein Compiler draus macht.
Gruß,
Svenska