Question Variable d'adresse en C


Pour les applications intégrées, il est souvent nécessaire d'accéder à des emplacements de mémoire fixes pour les registres de périphériques. La manière standard que j'ai trouvé pour faire ceci est quelque chose comme ceci:

// access register 'foo_reg', which is located at address 0x100
#define foo_reg *(int *)0x100

foo_reg = 1;      // write to foo_reg
int x = foo_reg;  // read from foo_reg

Je comprends comment cela fonctionne, mais ce que je ne comprends pas, c'est comment l'espace pour foo_reg est alloué (c'est-à-dire ce qui empêche l'éditeur de liens de placer une autre variable à 0x100?). L'espace peut-il être réservé au niveau C, ou doit-il y avoir une option de l'éditeur de liens qui spécifie que rien ne doit être situé à 0x100. J'utilise les outils GNU (gcc, ld, etc.), je m'intéresse donc principalement aux spécificités de cet ensemble d'outils pour le moment.

Quelques informations supplémentaires sur mon architecture pour clarifier la question:

Mon processeur s'interface avec un FPGA via un ensemble de registres mappés dans l'espace de données normal (où les variables sont actives) du processeur. J'ai donc besoin de pointer vers ces registres et de bloquer l'espace d'adressage associé. Dans le passé, j'ai utilisé un compilateur avec une extension pour localiser les variables du code C. Je regrouperais les registres en une structure, puis placerais la structure à l’endroit approprié:

typedef struct
{ 
   BYTE reg1;
   BYTE reg2;
   ...
} Registers;

Registers regs _at_ 0x100;

regs.reg1 = 0;

En fait, la création d'une structure 'Registers' réserve l'espace dans les yeux du compilateur / éditeur de liens.

Maintenant, en utilisant les outils GNU, je n'ai évidemment pas le à extension. En utilisant la méthode du pointeur:

#define reg1 *(BYTE*)0x100;
#define reg2 *(BYTE*)0x101;
reg1 = 0

// or
#define regs *(Registers*)0x100
regs->reg1 = 0;

Ceci est une application simple sans OS et sans gestion de mémoire avancée. Essentiellement:

void main()
{
    while(1){
        do_stuff();
    }
}

16
2018-03-25 17:53


origine


Réponses:


Votre éditeur de liens et votre compilateur ne sont pas au courant (sans que vous le disiez, bien sûr). Il appartient au concepteur de l'ABI de votre plate-forme de spécifier qu'ils n'allouent pas d'objets à ces adresses.

Il y a donc parfois (la plate-forme sur laquelle je travaillais) une plage dans l'espace d'adressage virtuel mappée directement sur des adresses physiques et une autre plage utilisable par les processus d'espace utilisateur pour développer la pile ou allouer de la mémoire.

Vous pouvez utiliser l'option defsym avec GNU ld pour allouer un symbole à une adresse fixe:

--defsym symbol=expression

Ou si l'expression est plus compliquée que l'arithmétique simple, utilisez un script d'éditeur de liens personnalisé. C'est l'endroit où vous pouvez définir des régions de mémoire et indiquer à l'éditeur de liens quelles régions doivent être attribuées à quelles sections / quels objets. Voir ici pour une explication. Bien que cela soit généralement le travail de l'auteur de la chaîne d'outils que vous utilisez. Ils prennent les spécifications de l'ABI, puis écrivent des scripts d'éditeur de liens et des back-end d'assembleur / compilateur répondant aux exigences de votre plate-forme.

Incidemment, GCC a un attribut section que vous pouvez utiliser pour placer votre structure dans une section spécifique. Vous pouvez alors demander à l'éditeur de liens de placer cette section dans la région où vivent vos registres.

Registers regs __attribute__((section("REGS")));

12
2018-03-25 18:03



Un éditeur de liens utilise généralement un script de l'éditeur de liens pour déterminer où les variables seraient allouées. Cela s'appelle la section "data" et devrait bien sûr pointer vers un emplacement RAM. Par conséquent, il est impossible qu'une variable soit attribuée à une adresse qui n'est pas dans la RAM.

Vous pouvez en savoir plus sur les scripts de l'éditeur de liens dans GCC ici.


9
2018-03-25 18:03



Votre éditeur de liens gère le placement des données et des variables. Il connaît votre système cible via un script de liaison. Le script de l'éditeur de liens définit les régions dans un disposition de la mémoire tel que .text (pour les données et le code constants) et .bss (pour vos variables globales et le tas), et crée également une corrélation entre une adresse virtuelle et une adresse physique (si nécessaire). Le responsable du script de l'éditeur de liens doit s'assurer que les sections utilisables par l'éditeur de liens ne remplacent pas les adresses d'E / S.


5
2018-03-25 17:54



Lorsque le système d'exploitation intégré charge l'application en mémoire, elle le charge généralement à un emplacement spécifié, disons 0x5000. Toute la mémoire locale que vous utilisez sera relative à cette adresse, c'est-à-dire que int x sera quelque part comme 0x5000 + taille du code + 4 ... en supposant qu'il s'agit d'une variable globale. S'il s'agit d'une variable locale, il est situé sur la pile. Lorsque vous faites référence à 0x100, vous faites référence à l'espace mémoire du système, au même espace que le système d'exploitation est chargé de gérer, et probablement à un endroit très spécifique qu'il surveille.

L'éditeur de liens ne place pas de code à des emplacements de mémoire spécifiques, il fonctionne dans «par rapport à l'endroit où mon code de programme est« l'espace mémoire ».

Cela se casse un peu lorsque vous entrez dans la mémoire virtuelle, mais pour les systèmes embarqués, cela a tendance à être vrai.

À votre santé!


3
2018-03-26 08:20



Obtenir la chaîne d'outils GCC pour vous donner une image utilisable directement sur le matériel sans OS pour le charger est possible, mais implique quelques étapes qui ne sont normalement pas nécessaires pour les programmes normaux.

  1. Vous aurez certainement besoin de personnaliser le module de démarrage de C runtime. Ceci est un module d'assemblage (souvent nommé quelque chose comme crt0.s) qui initialise les données initialisées, efface le BSS, appelle les constructeurs pour les objets globaux si des modules C ++ avec des objets globaux sont inclus, etc. Les personnalisations typiques incluent la nécessité de configurer votre matériel pour réellement adresser la RAM (y compris la configuration de la DRAM). contrôleur ainsi) pour qu’il y ait une place pour mettre les données et les empiler. Certains processeurs doivent faire ces choses dans une séquence spécifique: par ex. Le ColdFire MCF5307 possède une puce qui répond à chaque adresse après le démarrage et qui doit éventuellement être configurée pour couvrir uniquement la zone de la carte mémoire prévue pour la puce connectée.

  2. Votre équipe matérielle (ou vous-même avec un autre titre, éventuellement) devrait avoir une carte mémoire documentant ce qui se trouve à différentes adresses. ROM à 0x00000000, RAM à 0x10000000, périphérique inscrit à 0xD0000000, etc. Dans certains processeurs, l'équipe matérielle n'a peut-être connecté qu'une sélection de puces de la CPU à un périphérique et vous laisse le choix des déclencheurs d'adresse qui sélectionnent la broche. .

  3. GNU ld prend en charge un langage de script de l'éditeur de liens très flexible qui permet aux différentes sections de l'image exécutable d'être placées dans des espaces d'adressage spécifiques. Pour la programmation normale, vous ne voyez jamais le script de l'éditeur de liens car un fichier stock est fourni par gcc et adapté aux hypothèses de votre système d'exploitation pour une application normale.

  4. La sortie de l'éditeur de liens est dans un format relogeable destiné à être chargé dans la mémoire vive par un système d'exploitation. Il a probablement des corrections de relocalisation qui doivent être effectuées et peut même charger dynamiquement certaines bibliothèques. Dans un système ROM, le chargement dynamique n'est généralement pas pris en charge, vous ne le ferez donc pas. Mais vous avez toujours besoin d'une image binaire brute (souvent dans un format HEX adapté à un programmeur de PROM d'une certaine forme), vous devrez donc utiliser l'utilitaire objcopy de binutil pour transformer la sortie de l'éditeur de liens en un format approprié.

Donc, pour répondre à la question que vous avez posée ...

Vous utilisez un script de l'éditeur de liens pour spécifier les adresses cibles de chaque section de l'image de votre programme. Dans ce script, vous disposez de plusieurs options pour traiter les registres de périphériques, mais tous impliquent de mettre le texte, les données, la pile bss et les segments de segment dans des plages d'adresses qui évitent les registres matériels. Il existe également des mécanismes disponibles pour vous assurer que ld génère une erreur si vous remplissez trop votre ROM ou votre RAM, et vous devez également les utiliser.

En fait, obtenir les adresses de périphérique dans votre code C peut être fait avec #define comme dans votre exemple, ou en déclarant un symbole directement dans le script de l'éditeur de liens résolu à l'adresse de base des registres, avec une correspondance extern déclaration dans un fichier d'en-tête C.

Bien qu'il soit possible d'utiliser GCC section attribut pour définir une instance d'un fichier non initialisé struct comme étant situé dans une section spécifique (comme FPGA_REGS), J'ai trouvé que ne pas bien fonctionner dans des systèmes réels. Cela peut créer des problèmes de maintenance, et cela devient un moyen coûteux de décrire la carte d'enregistrement complète des périphériques intégrés. Si vous utilisez cette technique, le script de l'éditeur de liens sera alors responsable du mappage FPGA_REGS à sa bonne adresse.

Dans tous les cas, vous aurez besoin de bien comprendre les concepts de fichier objet tels que les "sections" (en particulier les sections text, data et bss au minimum), et vous devrez peut-être rechercher les détails qui comblent le fossé entre les composants matériels. et des logiciels tels que la table de vecteurs d'interruption, les priorités d'interruption, les modes superviseur / utilisateur (ou les sonneries 0 à 3 sur les variantes x86) et similaires.


3
2018-03-25 17:49



Généralement, ces adresses sont hors de portée de votre processus. Donc, votre éditeur de liens n'oserait pas y mettre des choses.


1
2018-03-25 17:53



Si l'emplacement de la mémoire a une signification particulière sur votre architecture, le compilateur doit le savoir et ne pas y placer de variables. Cela serait similaire à l’espace mappé IO sur la plupart des architectures. Il ne sait pas que vous l'utilisez pour stocker des valeurs, il sait juste que les variables normales ne doivent pas y aller. De nombreux compilateurs intégrés prennent en charge les extensions de langage qui vous permettent de déclarer des variables et des fonctions à des emplacements spécifiques, en utilisant généralement #pragma. Aussi, généralement, la façon dont j'ai vu des personnes implémenter le type de mappage de mémoire que vous essayez de faire est de déclarer un int à l'emplacement de mémoire souhaité, puis de le traiter comme une variable globale. Vous pouvez également déclarer un pointeur sur un int et l'initialiser à cette adresse. Les deux offrent plus de sécurité de type qu'une macro.


1
2018-03-25 18:50



Pour élargir la réponse de litb, vous pouvez également utiliser le --just-symbols={symbolfile} option pour définir plusieurs symboles, au cas où vous auriez plusieurs périphériques mappés en mémoire. Le fichier de symboles doit être au format

symbolname1 = address;
symbolname2 = address;
...

(Les espaces autour du signe égal semblent être requis.)


1
2018-03-25 18:07



Souvent, pour les logiciels intégrés, vous pouvez définir dans le fichier de l'éditeur de liens une zone de mémoire vive pour les variables affectées par l'éditeur de liens et une zone distincte pour les variables situées à des emplacements absolus.

Ne pas le faire devrait provoquer une erreur de l'éditeur de liens, car il devrait détecter qu'il tente de placer une variable à un emplacement déjà utilisé par une variable avec une adresse absolue.


1