Przeglądaj źródła

Add some notes and planning of the document

Alexandre Janniaux 6 lat temu
rodzic
commit
375476e90f
1 zmienionych plików z 281 dodań i 0 usunięć
  1. 281 0
      vlc/02_organisation.md

+ 281 - 0
vlc/02_organisation.md

@@ -0,0 +1,281 @@
+Le document s'organise de la façon suivante
+
++ Architecture de VLC
++ Présentation des modèles et abstractions utilisés dans la sandbox
++ Implémentation Linux des abstractions
++ Implémentation Windows des abstractions
+
+===
+
+Le principal de l'architecture actuelle de VLC est organisé autour de la notion
+de module. D'un côté, le core contrôle le chargement des fonctionnalités et joue
+un rôle d'orchestrateur. De l'autre côté chaque fonctionnalité est apportée par
+un module qui peut être chargé dynamiquement ou lié statiquement à
+l'application.
+
+Les modules sont manipulés à travers les `vlc_plugin_t` qui eux-même manipulent
+les `module_t` contenant la représentation interne du module.
+
+```
+typedef struct vlc_plugin_t
+{
+    struct vlc_plugin_t *next;
+    module_t *module;
+    unsigned modules_count;
+
+    const char *textdomain; /**< gettext domain (or NULL) */
+
+    /* Variables set by the module to store its config options */
+    struct
+    {
+        module_config_t *items; /**< Table of configuration parameters */
+        size_t size; /**< Size of items table */
+        size_t count; /**< Number of configuration items */
+        size_t booleans; /**< Number of booleal config items */
+    } conf;
+
+#ifdef HAVE_DYNAMIC_PLUGINS
+    bool unloadable; /**< Whether the plug-in can be unloaded safely */
+    atomic_uintptr_t handle; /**< Run-time linker handle (or nul) */
+    char *abspath; /**< Absolute path */
+
+    char *path; /**< Relative path (within plug-in directory) */
+    int64_t mtime; /**< Last modification time */
+    uint64_t size; /**< File size */
+#endif
+} vlc_plugin_t;
+```
+
+`vlc_plugin_t` contient ainsi les informations pour charger et décharger les
+modules.
+
+```
+struct module_t
+{
+    vlc_plugin_t *plugin; /**< Plug-in/library containing the module */
+    module_t   *next;
+
+    /** Shortcuts to the module */
+    unsigned    i_shortcuts;
+    const char **pp_shortcuts;
+
+    /*
+     * Variables set by the module to identify itself
+     */
+    const char *psz_shortname;                              /**< Module name */
+    const char *psz_longname;                   /**< Module descriptive name */
+    const char *psz_help;        /**< Long help string for "special" modules */
+
+    const char *psz_capability;                              /**< Capability */
+    int      i_score;                          /**< Score for the capability */
+
+    /* Callbacks */
+    const char *activate_name;
+    const char *deactivate_name;
+    void *pf_activate;
+    void *pf_deactivate;
+};
+```
+
+De l'autre côté, `module_t` est réellement la description d'un module.
+
+Chaque module dispose d'une priorité, qui correspond au score `i_score` dans
+`module_t`.
+
+Ces modules sont chargés dans l'application depuis deux fonctions du core :
++ `module_need` permet de charger un module correspondant au nom ou à la
+fonction `i_capability` du module, en utilisant le premier module de plus forte
+priorité qui est capable de se charger sans erreur.
++ `module_need_var` qui dans les faits appelle `module_need` en lui donnant le
+contenu d'une variable au lieu d'une recherche hardcodée.
+
+```
+    /* from src/misc/xml.c */
+    p_xml = vlc_custom_create( p_this, sizeof( *p_xml ), "xml" );
+    p_xml->p_module = module_need( p_xml, "xml", NULL, false );
+
+    reader = vlc_custom_create(obj, sizeof(*reader), "xml reader");
+    reader->p_module = module_need(reader, "xml reader", NULL, false);
+```
+
+Par exemple, nous avons ici la création d'un objet représentant un module
+d'analyse syntaxique XML. Le module est ensuite chargé dans l'objet. Puis un
+autre couple d'objet et module est chargé, cette fois-ci pour une `i_capability`
+de type `xml_reader`.
+
+TODO: décrire module_need en détail et comment on peut vouloir la patcher pour
+prendre en compte la sandbox.
+
++ parler de libvlc et vlc_object
++ parler des variables VLC
+
+
+```
+module_t *vlc_module_load(vlc_object_t *obj, const char *capability,
+                          const char *name, bool strict,
+                          vlc_activate_t probe, ...);
+
+module_t *module_need(vlc_object_t *obj, const char *cap, const char *name,
+                      bool strict)
+```
+
+Les détails de ces fonctions sont décrits ici car il s'agit de l'interface
+exposée par VLC pour charger un nouveau module. C'est donc un point d'entrée
+potentiel pour l'injection des abstractions de la sandbox.
+
+
+# Abstractions apportées par la sandbox
+
+## Architecture : broker vs non broker
+
+
+### Le modèle broker
+
+Dans ce modèle, on considère un processus privilégié qu'on appelle broker, qui
+va contrôler tous les échanges entre les différents processus qu'on appelle
+workers, mais également leur cycle de vie et leurs permissions et accès.
+
+Le broker est donc en charge de faire le routage des différents messages, de
+transmettre les ressources et faire la vérification d'accès. Il est également
+utile pour vérifier que les processus workers sont encore en vie et signaler les
+erreurs.
+
+
+### Modèle par capabilities et orchestrateur
+
+Meilleures performances. On garde un «orchestrateur» pour initialiser mais les
+modules peuvent fonctionner sans après.
+
+## Communication inter-processus
+
+On a besoin d'un moyen de créer une communication inter-processus et de pouvoir
+transéfer la capacité de communiquer dessus à travers les processus.
+
+On introduit donc le type `vlc_process_ipc_t` qui est capable de stocker ce bus
+de communication et on construira les fonctions de communication dessus.
+
+On a également besoin d'un moyen de sérialiser et envoyer des messages pour
+construire les appels de fonctions déportés (RPC, pour Remote Procedure Call).
+Cela va notamment nous servir à calquer l'architecture actuelle de VLC pour la
+faire évoluer progressivement vers un modèle plus découplé et moins contraint.
+
+## Initialisation des liens entre modules
+
+On imagine que la situation est la même peu importe que l'on soit dans une
+architecture broker ou une architecture orchestrateur. C'est-à-dire qu'on peut
+de toute façon facilement passer d'une architecture non broker à une
+architecture broker avec la construction suivante :
+
+On représente chaque lien entre processus par une IPC du point de vue des
+workers. Il s'agit du cas avec orchestrateur. Si on passe en sandbox avec modèle
+broker, chaque lien est cassé en deux avec une paire worker-broker et une autre
+paire broker-worker, et le broker s'assure ainsi de faire le routage et vérifier
+les accès. On a donc découplé le fonctionnement du worker de la politique
+d'accès choisie. Cette propriété est très importante pour la suite, car elle
+permet de construire le modèle puis d'adapter l'implémentation à ce qui se
+rapproche le plus du fonctionnement du système sous-jacent.
+
+## Gestion de la boucle d'événement
+
+On peut donc commencer à construire chaque worker. On imagine que chacun est
+capable de communiquer à l'extérieur vers la bonne partie du core à partir du
+moment où l'initialisation a fait partager une paire d'IPC pour les relier.
+
+On va donc devoir gérer les messages envoyés d'un processus à l'autre.
+Exactement, il va falloir raisonner en parlant des messages envoyés des
+processus vers le processus qu'on étudie en particulier. Pour s'intégrer plus
+facilement dans l'application, on ne va pas intégrer la gestion des événements
+directement dans le flot d'exécution de chaque partie du core. En effet, cela
+deviendrait difficile de raisonner en terme de zone de privilège étant donné que
+l'application elle-même (et non plus seulement la sandbox) devra savoir router
+les messages d'un module supérieur vers un module inférieur.
+
+On va donc créer un thread par processus qui sera chargé de recevoir et exécuter
+les messages puis exécuter les RPC : il s'agit donc d'une boucle événementielle
+assez classique, mais qui doit prendre en compte le modèle threadé de
+l'application sous-jacente. Une attention particulière doit être mise sur la
+gestion des deadlocks.
+
+Une IPC qui déclencherait une fonction d'un morceau de l'application qui
+déclencherait une nouvelle RPC dans un autre processus et attendrait le retour
+de fonction amènerait à une situation de deadlock, la boucle événementielle
+étant bloquée dans l'exécution de la première RPC.
+
+Pire encore, une RPC créant le verrouillage de'certains mutexes qui seraient
+également verrouillés par le résultat de la RPC aboutirait également au blocage
+complet de ce bout de l'application.
+
+Une fois ces problématiques en tête, il faut prendre en compte les mécanismes
+des systèmes d'exploitations pour la programmation asynchrone et écrire une
+abstraction qui permet de récupérer un message depuis l'une des IPC disponibles.
+C'est ainsi qu'apparaît `vlc_msg_poller_t` et l'API qui est liée.
+
+```
+// TODO: mettre structure et API
+```
+
+## Gestion des blocs mémoires
+
+Lorsque l'on traite des données en streaming, on récupère la plupart du temps
+des tronçons de données. Dans VLC, ces tronçons sont annotés de données
+multimédia : le dts et le pts par exemple. On verra son fonctionnement en
+détails dans la partie encodage matériel pour Android.
+
+Ce type de données est particulièrement présent dans le pipeline multimédia de
+VLC. On obtient des `block_t` après une lecture depuis un fichier ou le réseau,
+qui est transmis au `demux_t` qui va analyser et extraire différents flux audio,
+vidéo et sous-titre. Il crée un `block_t` pour chacune des unités correspondant
+au format de ces flux, on peut par exemple penser à une image pour un flux
+MJPEG. Ces blocs sont ensuite transmis au décodeur correspondant qui va créer la
+`picture_t` pour la vidéo, ou `subpicture_t` pour les sous-titres, ou `block_t`
+pour l'audio et la passer dans la chaîne de filtre pour l'envoyer à la sortie
+correspondante.
+
+Il faut désormais repenser à la situation de la sandbox et se souvenir que ces
+données-là vont devoir transiter d'un processus à l'autre de façon efficace. On
+va donc essayer de s'intéresser aux allocations effectuées par les modules et
+intégrer cette gestion en utilisant les mécanismes du système d'exploitation.
+Dans tous les cas, le système d'exploitation dispose d'un mécanisme de mémoire
+partagée pour dresser la correspondance de pages mémoires dans des processus
+différents. On peut donc essayer d'introduire des `block_t` alloués comme des
+mémoires partagées que l'on transmet de processus en processus.
+
+Avec cette idée-là, la gestion de l'accès des processsus aux ressources devient
+cruciale.
+
+## Gestion des objets libvlccore
+
+Armé des différentes abstractions mises en place pour la communication
+inter-processus des données de contrôle et des données applicatives, on va
+pouvoir commencer à intégrer réellement les morceaux de l'application dans la
+sandbox. On va donc devoir définir comment sera découpé et cloisonné
+l'application et quels seront les points d'entrées pour la sandbox.
+
+Dans cette partie, nous allons majoritairement prendre en considération le cas
+du découpage du décodeur, entraînant la refactorisation d'autres dépendences de
+cet objet.
+
+## Découpage en module
+
+Une autre méthode de cloisonnement qui n'a pas beaucoup été explorée est de
+cloisonner l'application par module et non par objet de libvlccore.
+
+Cette méthode permet d'arriver plus rapidement à une situation où l'ensemble des
+modules de VLC est cloisonné, mais libvlccore ne profite pas du tout de ce
+cloisonnement et il nous reste alors environ 100 000 lignes de code à auditer
+pour s'approcher de loin à la méthode précédente. La technique reste
+intéressante à mentionner comme l'injection montre de façon plus simple comment
+le pattern fonctionne.
+
+
+# Implémentation sous Linux
+
+## Passage de descripteur de fichiers entre processus
+
+## Gestion asynchrone des messages reçus
+
+Modèle par readiness
+
+## Routage des messages
+
+Socket unix