Pirater les appels système de Linux

Récemment, j'ai publié un article intitulé "Introduction à l'API Socket" pour apprendre en langage C, et j'ai écrit un programme utilisant TCP et UDP pour le moment, il est donc temps pour le traitement de parallélisation et le traitement de multiplexage qui sont indispensables pour la programmation réseau. Je pense déménager.

Donc, cette fois, je vais me plonger dans le traitement multi-processus. Fork est un appel système important pour réaliser des multi-processus. À l'origine, je savais que l'appel système qui engendrait les processus enfants sous Linux était fork, et je savais en quelque sorte l'utiliser.

Cependant, je ne savais pas par quel type d'implémentation il avait été réalisé, j'ai donc vérifié correctement le code source à ce moment.

Cependant, il y avait des moments où il était difficile de suivre uniquement le code source, donc dans un tel cas, pour le moment, sortez le code approprié en utilisant la fonction fork vers le code objet lié statiquement à gcc, et supprimez-le avec objdump. Lors de l'assemblage, je faisais un travail extrêmement simple en comparant le flux du code source avec le langage machine qui était réellement produit.

Je ne le comprends pas à 100%, et je suis sceptique quant à la bonne interprétation, mais je continuerai d'enquêter et de le mettre à jour petit à petit afin de pouvoir en garder des exactes.

Et si vous le connaissez, je vous serais très reconnaissant de bien vouloir me donner quelques conseils. Nous recherchons également des amis qui peuvent lire ensemble le code open source au café w

Code source référencé

Noyau Linux: 2.6.11 commandes proc: procps-3.2.8 glibc:glibc-2.12.1 Le processeur est x86_64.

Qu'est-ce qu'un processus

Un processus est un espace d'adressage pour exécuter un programme et une collection d'informations nécessaires au traitement.

Le programme est généralement stocké dans un périphérique de stockage auxiliaire tel qu'un disque dur, mais il est chargé en mémoire et exécuté dans le processus.

Le programme le plus important qui compose le système d'exploitation s'appelle le noyau, mais le processus est géré par le noyau chargé en mémoire.

Lorsque le processeur fonctionne sous Linux, il existe deux modes, le mode utilisateur et le mode noyau. En mode noyau, les ressources système sont accessibles sans restrictions, de sorte que le traitement lié au matériel et à l'ensemble du système peut être effectué par l'utilisateur. Le mode vous permet d'effectuer le traitement des applications dans un espace d'adressage unique qui ne nécessite pas d'accès à ces ressources.

Cependant, en émettant des appels système dans le processus, il est possible de basculer temporairement le CPU en mode noyau et d'exécuter des instructions dépendant du CPU. À propos, un appel système est un groupe spécifique de processus qui sont demandés par un processus au système d'exploitation pour être exécutés en mode noyau.

Utilisons maintenant la commande ps avec des options pour lister et afficher les processus qui existent actuellement sur mon Linux.

ps -ef f

root     21449     1  0 21:34 ?        Ss     0:00 php-fpm: master process (/etc/php-fpm.conf)
apache   21450 21449  0 21:34 ?        S      0:00  \_ php-fpm: pool www

Un processus a des informations dans une structure task_struct appelée descripteur de processus, et a un membre de type pid_t qui a une correspondance biunivoque avec le processus appelé ID de processus. Dans ce cas, 21449 et 21450 dans la deuxième colonne signifient l'ID de processus.

La structure task_struct est définie dans ʻinclude / linux / sched.h` dans le noyau Linux, mais comme il s'agissait d'une structure de près de 200 lignes, je m'abstiendrai de la citer. C'est une structure importante remplie d'informations sur le processus, c'est donc une bonne idée de la lire pendant les longues nuits d'automne.

Et la troisième colonne affiche l'ID de processus du processus qui a créé le processus. Certains processus sont généralement générés dans un processus parent.

Vous pouvez voir que le processus parent sur la deuxième ligne est le processus sur la première ligne, mais l'ID de processus parent du processus sur la première ligne est 1.

Cela représente un processus appelé init, qui prend le relais comme un parent d'accueil lorsque le processus parental n'existe pas ou que le parent meurt avant l'enfant. L'init est créé par le noyau et est également l'ancêtre de tous les processus.

Nous vérifierons l'état dans lequel init est le parent en exécutant le programme plus tard, mais d'abord, écrivons et exécutons simplement un programme qui génère un processus enfant.

python


#include <sys/types.h> /* pid_t */
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {

    pid_t pid, ret;

    printf("start!\n");

    if((ret = fork()) == -1){
        perror("fork() failed.");
        exit(EXIT_FAILURE);
    }
    pid = getpid();

    printf("return from fork() is %d\n", ret);
    printf("pid is %d\n", pid);

    printf("end!\n");

    return 0;
}

Tout d'abord, il affiche start!, Et la fonction fork effectue un appel système fork (qui sera révélé plus tard, mais clone en interne). Fork () renvoie -1 en cas d'échec, auquel cas il met immédiatement fin à l'exécution du programme.

Si fork () réussit, récupérez l'ID du processus avec la fonction getpid pour obtenir l'ID du processus actuel et l'afficher. Ici, le membre tgid, qui est l'ID de processus du chef de groupe de threads, est acquis à la place du membre pid, qui indique l'ID de processus exact de la structure de descripteur de processus.

L'appel système getpid appelle la fonction sys_getpid dans le noyau, mais le code source est implémenté comme suit à la ligne 967 de kernel / timer.c.

c:linux-2.6.11.1_kernel/timer.c


asmlinkage long sys_getpid(void)
{
    return current->tgid;
}

La variable courante ici est un pointeur vers le descripteur de processus courant, la structure de type task_struct, en fonction du processeur. Vous pouvez voir qu'il pointe vers le membre tgid, pas le membre pid de l'objet de structure de type task_struct.

Cette différence a des implications importantes lorsque l'on considère la programmation multi-thread, pas multi-processus, alors gardez-la dans le coin de votre cœur.

Et l'ID de processus de la commande ps montre également le membre tgid. Ce qui suit fait partie du code source de la commande ps, mais pour afficher la liste des processus, chargez le processus sous / proc et placez l'ID de processus dans le membre tgid et le membre tid via p qui est un pointeur vers la structure proc_t. Il est stocké.

c:procps-3.2.8_proc/readproc.c


static int simple_nextpid(PROCTAB *restrict const PT, proc_t *restrict const p) {
  static struct direct *ent;        /* dirent handle */
  char *restrict const path = PT->path;
  for (;;) {
    ent = readdir(PT->procfs);
    if(unlikely(unlikely(!ent) || unlikely(!ent->d_name))) return 0;
    if(likely( likely(*ent->d_name > '0') && likely(*ent->d_name <= '9') )) break;
  }
  p->tgid = strtoul(ent->d_name, NULL, 10); 
  p->tid = p->tgid;
  memcpy(path, "/proc/", 6);
  strcpy(path+6, ent->d_name);  // trust /proc to not contain evil top-level entries
  return 1;
}

fork renvoie 0 pour les processus enfants et l'ID de processus des processus enfants pour les processus parents. Voici le résultat de l'exécution du programme et l'état de la commande ps en cours d'exécution.

Résultat d'exécution


start!
return from fork() is 21526
pid is 21525
end!
return from fork() is 0
pid is 21526
end!

État de la commande ps


tajima   21181 21180  0 20:51 pts/0    Ss     0:00  |       \_ -bash
tajima   21525 21181  0 22:24 pts/0    S+     0:00  |           \_ ./a.out
tajima   21526 21525  0 22:24 pts/0    S+     0:00  |               \_ ./a.out

L'exécution du processus enfant ou du processus parent dépend du planificateur, mais dans ce cas, vous pouvez voir que le processus parent et le processus enfant ont été exécutés dans cet ordre.

Vous remarquerez que le début! Ne s'affiche qu'une seule fois ici. Cela est dû au fait que le nouveau processus démarre immédiatement après l'appel système fork. Vous comprendrez la raison plus tard car nous suivrons le traitement interne de fork.

Maintenant, changeons le programme précédent comme suit. Le fait qu'il s'agisse d'un processus parent ou d'un processus enfant est déterminé par la valeur de retour de la fonction getpid, mais la mise en veille est exécutée pendant 10 secondes pour le processus parent et 30 secondes pour le processus enfant.

python


    pid = getpid();

    if(ret == 0){ 
        sleep(30);
    } else {
        sleep(10);
    }   

    printf("return from fork() is %d\n", ret);
    printf("pid is %d\n", pid);

Si vous vérifiez avec la commande ps immédiatement après le démarrage du programme,

tajima   22501 22438  0 21:32 pts/0    S+     0:00  |           \_ ./a.out
tajima   22502 22501  0 21:32 pts/0    S+     0:00  |               \_ ./a.out

Le résultat est le même que précédemment, mais lorsque le processus parent se réveille et termine l'exécution du programme, si vous exécutez à nouveau la commande ps, tajima 22502 1 0 21:32 pts/0 S 0:00 ./a.out Vous pouvez voir que le parent du processus enfant est le processus avec l'ID de processus 1, c'est-à-dire init.

Dans certains cas, vous souhaiterez peut-être effectuer un processus synchronisé entre parent et enfant.

Il existe plusieurs façons de procéder, par exemple attendre que le processus parent termine le processus enfant ou détecter la fin du processus enfant avec un signal.

Le programme ci-dessous exécute l'appel système waitpid () pour bloquer l'exécution du processus parent jusqu'à ce que le processus enfant se termine.

Une fois le processus enfant terminé, le processus parent est détruit 10 secondes plus tard.

python


#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {

    pid_t pid, ret;

    printf("start\n");

    if((ret = fork()) == -1){
        perror("fork() failed.");
        exit(EXIT_FAILURE);
    }   
    pid = getpid();

    if(ret == 0){ 
        sleep(30);
    } else {
        if(waitpid(-1, NULL, 0) < 0) {
            perror("waitpid() failed.");
            exit(EXIT_FAILURE);
        }   
        sleep(10);
    }   

    printf("return from fork() is %d\n", ret);
    printf("pid is %d\n", pid);

}

waitpid () peut recevoir l'état de fin d'un processus enfant en passant un pointeur de type int comme argument du deuxième argument, ou changer son comportement en spécifiant une somme logique optionnelle dans le troisième argument.

Cette fois, il est supposé que le contrôle sera retourné au processus parent en raison du changement d'état de la fin du processus enfant, mais il est possible de détecter non seulement l'arrêt mais également le changement d'état tel que l'arrêt et le redémarrage.

Comme d'habitude, lorsque j'exécute la commande ps, ce qui suit s'affiche pendant un moment,

tajima   22438 22437  0 21:28 pts/0    Ss     0:00  |       \_ -bash
tajima   22609 22438  0 22:33 pts/0    S+     0:00  |           \_ ./a.out
tajima   22610 22609  0 22:33 pts/0    S+     0:00  |               \_ ./a.out

Vous pouvez voir que ce qui suit s'affiche lorsque le processus enfant se termine.

tajima   22438 22437  0 21:28 pts/0    Ss     0:00  |       \_ -bash
tajima   22609 22438  0 22:33 pts/0    S+     0:00  |           \_ ./a.out

De plus, le processus enfant est dans un processus zombie jusqu'à ce que le processus parent appelle un appel système d'attente pour nettoyer le processus. init nettoie en lançant un appel système wait4 pour devenir le parent de ce zombie perdu et libérer l'âme.

Fourchette de piratage

Jetons maintenant un œil à l'implémentation de fork. Ce qui suit est une version désassemblée d'un fichier exécutable qui est lié statiquement à un programme qui utilise la fonction fork.

fork.S


0000000000400494 <main>:
  400494:   55                      push   %rbp   
  400495:   48 89 e5                mov    %rsp,%rbp
  400498:   e8 f3 d1 00 00          callq  40d690 <__libc_fork>
  40049d:   b8 00 00 00 00          mov    $0x0,%eax
  4004a2:   c9                      leaveq 
  4004a3:   c3                      retq   

Nous appelons une fonction avec le symbole «__libc_fork». Donc, si vous suivez __libc_fork dans le même fichier, c'est exactement ce que fait le CPU, mais il est difficile de le lire soudainement, alors vérifiez d'abord l'implémentation avec le code source du langage C. Je vais.

c:glibc-2.12.1_nptl/sysdeps/unix/sysv/linux/fork.c


pid_t
__libc_fork (void)
{
  pid_t pid;
  struct used_handler
  {
    struct fork_handler *handler;
    struct used_handler *next;
  } *allp = NULL;

  /* Run all the registered preparation handlers.  In reverse order.
     While doing this we build up a list of all the entries.  */
  struct fork_handler *runp;
  while ((runp = __fork_handlers) != NULL)
    {   
      /* Make sure we read from the current RUNP pointer.  */
      atomic_full_barrier (); 

      unsigned int oldval = runp->refcntr;

      if (oldval == 0)
    /* This means some other thread removed the list just after
       the pointer has been loaded.  Try again.  Either the list
       is empty or we can retry it.  */

/*réduction*/

#ifdef ARCH_FORK
  pid = ARCH_FORK (); 
#else
# error "ARCH_FORK must be defined so that the CLONE_SETTID flag is used"
  pid = INLINE_SYSCALL (fork, 0); 
#endif
/*réduction*/

Si vous suivez la macro ARCH_FORK, vous rencontrerez une macro appelée INLINE_SYSCALL, nous allons donc la suivre plus loin.

c:glibc-2.12.1_nptl/sysdeps/unix/sysv/linux/x86_64/fork.c


#define ARCH_FORK() \
  INLINE_SYSCALL (clone, 4,                           \
          CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD, 0,     \
          NULL, &THREAD_SELF->tid)

c:glibc-2.12.1_sysdeps/unix/sysv/linux/x86_64/sysdep.h


# define INLINE_SYSCALL(name, nr, args...) \
  ({                                          \
    unsigned long int resultvar = INTERNAL_SYSCALL (name, , nr, args);        \
    if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (resultvar, ), 0))         \
      {                                       \
    __set_errno (INTERNAL_SYSCALL_ERRNO (resultvar, ));           \
    resultvar = (unsigned long int) -1;                   \
      }                                       \
    (long int) resultvar; })

# define INTERNAL_SYSCALL(name, err, nr, args...) \
  INTERNAL_SYSCALL_NCS (__NR_##name, err, nr, ##args)

# define INTERNAL_SYSCALL_NCS(name, err, nr, args...) \
  ({                                          \
    unsigned long int resultvar;                          \
    LOAD_ARGS_##nr (args)                             \
    LOAD_REGS_##nr                                \
    asm volatile (                                \
    "syscall\n\t"                                 \
    : "=a" (resultvar)                                \
    : "0" (name) ASM_ARGS_##nr : "memory", "cc", "r11", "cx");            \
    (long int) resultvar; })

La macro INLINE_SYSCALL est ensuite convertie en interne en une fonction macro appelée INTERNAL_SYSCALL_NCS.

À l'intérieur de cela, la valeur de __NR_clone est stockée dans le registre rax dans la description de l'assembleur en ligne. Vous pouvez voir que l'appel système de clonage est en cours d'exécution.

À propos, l'opérande de sortie = a signifie qu'il sort vers le registre rax, et l'opérande d'entrée 0 signifie que le registre d'entrée est également le même registre rax que la sortie.

c:linux-2.6.11.1_sysdeps/unix/sysv/linux/x86_64/sysdep.h


#define __NR_clone                              56

Vous pouvez voir que le numéro d'appel du système clone est 56 sur les processeurs de la série x86_64. Vérifions la preuve avec le code dans l'assembleur plus tôt.

fork.S


  40d736:   b8 38 00 00 00          mov    $0x38,%eax
  40d73b:   0f 05                   syscall

Le nombre hexadécimal 0x38 est stocké dans le registre eax (rax). Cela signifie le nombre décimal 56.

L'appel système clone () est implémenté par la fonction sys_clone, qui appelle en interne la fonction do_fork.

c:linux-2.6.11.1arch/x86_64/kernel/process.c


asmlinkage long sys_clone(unsigned long clone_flags, unsigned long newsp, void __user *parent_tid, void __user *child_tid, struct pt_regs *regs)
{
    if (!newsp)
        newsp = regs->rsp;
    return do_fork(clone_flags, newsp, regs, 0, parent_tid, child_tid);
}

c:linux-2.6.11.1_kernel/fork.c_1124-1134


long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    struct task_struct *p;
    int trace = 0; 
    long pid = alloc_pidmap();

Gérez l'utilisation du PID avec long pid = alloc_pidmap (); Obtenez un nouveau PID pour un processus enfant à partir d'un tableau appelé pidmap_array.

c:linux-2.6.11.1_kernel/pid.c


typedef struct pidmap {                                                
    atomic_t nr_free;                                                  
    void *page;                                                        
} pidmap_t;       

static pidmap_t pidmap_array[PIDMAP_ENTRIES] =                         
     { [ 0 ... PIDMAP_ENTRIES-1 ] = { ATOMIC_INIT(BITS_PER_PAGE), NULL } };

pidmap_array a une structure appelée struct pidmap en tant qu'élément et est typé au type pidmap_t.

c:linux-2.6.11.1_kernel/fork.c_1142-1143



    p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);

La fonction copy_process renvoie un pointeur vers la structure task_struct, qui est une copie du descripteur de processus du processus parent. Ci-dessous, je ne regarderai que les points principaux.

c:linux-2.6.11.1_kernel/fork.c_820


p = dup_task_struct(current); 

current pointe vers un pointeur vers la structure task_struct du processus courant, mais copie ce contenu dans la structure task_struct du processus enfant et renvoie ce pointeur. Après cela, divers processus d'initialisation sont exécutés sur le pointeur vers la structure task_struct du processus enfant copié.

Ensuite, la fonction copy_thread est appelée avec le pointeur vers la structure pt_regs comme argument pour définir la pile en mode noyau du processus enfant. La structure pt_regs stocke la valeur du registre lorsque le mode noyau est appelé.

include/asm-x86_64/ptrace.h_39-65


struct pt_regs {
    unsigned long r15;
    unsigned long r14;
    unsigned long r13;
    unsigned long r12;
    unsigned long rbp;
    unsigned long rbx;
/* arguments: non interrupts/non tracing syscalls only save upto here*/
    unsigned long r11;
    unsigned long r10;  
    unsigned long r9; 
    unsigned long r8; 
    unsigned long rax;
    unsigned long rcx;
    unsigned long rdx;
    unsigned long rsi;
    unsigned long rdi;
    unsigned long orig_rax;
/* end of arguments */  
/* cpu exception frame or undefined */
    unsigned long rip;
    unsigned long cs; 
    unsigned long eflags; 
    unsigned long rsp; 
    unsigned long ss; 
/* top of stack page */ 
};

c:linux-2.6.11.1_arch/x86_64/kernel/process.c_366-386


int copy_thread(int nr, unsigned long clone_flags, unsigned long rsp, 
        unsigned long unused,
    struct task_struct * p, struct pt_regs * regs)
{
    int err;
    struct pt_regs * childregs;
    struct task_struct *me = current;

    childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p->thread_info)) - 1;

    *childregs = *regs;

    childregs->rax = 0;
    childregs->rsp = rsp;
    if (rsp == ~0UL) {
        childregs->rsp = (unsigned long)childregs;
    }   

    p->thread.rsp = (unsigned long) childregs;
    p->thread.rsp0 = (unsigned long) (childregs+1);
    p->thread.userrsp = me->thread.userrsp; 

childregs = ((struct pt_regs *) (THREAD_SIZE + (unsigned long) p-> thread_info)) --1; Ce processus place la structure pt_regs à l'adresse supérieure de la pile du noyau dans la zone mémoire allouée au processus. C'est le traitement de. Je pense que c'est une implémentation que l'adresse obtenue en soustrayant la taille 1 de la structure pt_regs devient l'emplacement de stockage de pt_regs.

Pour les childregs, tout le contenu des regs est copié une fois, puis la valeur du registre est partiellement modifiée.

Ce à quoi vous devez faire attention ici est childregs-> rax = 0. J'ai mis 0 dans le registre rax. En langage C, la valeur de retour est censée être retournée dans le registre rax ou eax, donc dans le cas d'un processus enfant, il s'agit de la valeur de retour qui est renvoyée à l'espace de processus utilisateur tel quel.

L'adresse de niveau supérieur de la pile est stockée dans p-> thread.rsp0.

c:linux-2.6.11.1_kernel/fork.c_32-38


        if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {                                                                      
            /*
             * We'll start up with an immediate SIGSTOP.              
             */ 
            sigaddset(&p->pending.signal, SIGSTOP);                   
            set_tsk_thread_flag(p, TIF_SIGPENDING);                   
        }           

Si le processus enfant est surveillé par le débogueur ou si l'indicateur CLONE_STOPPED est défini, l'exécution du processus enfant doit être arrêtée.

c:linux-2.6.11.1_kernel/fork.c_40-43


        if (!(clone_flags & CLONE_STOPPED))
            wake_up_new_task(p, clone_flags);                         
        else
            p->state = TASK_STOPPED;

Si l'indicateur CLONE_STOPPED n'est pas défini, exécutez la fonction wake_up_new_task pour ajuster correctement la planification du processus parent-enfant, et si l'indicateur CLONE_STOPPED est défini, définissez l'indicateur TASK_STOPPED dans le membre d'état.

c:linux-2.6.11.1_kernel/fork.c_45-48


        if (unlikely (trace)) {
            current->ptrace_message = pid; 
            ptrace_notify ((trace << 8) | SIGTRAP);
        }    

Il s'agit d'un processus pour le débogueur. Ce processus permet au débogueur de suivre correctement les processus parent et enfant.

c:linux-2.6.11.1_kernel/fork.c_50-54


        if (clone_flags & CLONE_VFORK) {
            wait_for_completion(&vfork);
            if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))
                ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);
        }  

Il s'agit du processus lorsque l'appel système vfork () est utilisé. Étant donné que vfork partage le même espace d'adressage mémoire avec les processus parent et enfant, il arrête l'exécution du processus parent jusqu'à ce que le processus enfant se termine. Comme cela sera décrit plus loin, c'est l'un des dispositifs mis en œuvre par Linux pour éliminer le gaspillage de réplication par fourches.

c:linux-2.6.11.1_arch/x86_64/kernel/process.c


asmlinkage long sys_vfork(struct pt_regs *regs)
{
    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->rsp, regs, 0,
            NULL, NULL);
}

c:linux-2.6.11.1_kernel/fork.c_55-59


    } else {
        free_pidmap(pid);
        pid = PTR_ERR(p);
    }
    return pid;

Si le traitement avec la fonction copy_process échoue, l'ID de processus acquis pour le processus enfant est libéré en définissant un indicateur inutilisé dans pidmap_array, puis converti en un code d'erreur et stocké dans pid. S'il n'échoue pas, l'ID de processus du processus enfant obtenu tel quel sera renvoyé.

Ensuite, le processus retourne à «__libc_fork» en mode utilisateur, se branche dans le processus parent et le processus enfant selon que le pid est 0 et effectue un post-traitement approprié. Il renvoie ensuite l'ID de processus au processus utilisateur.

Une copie du processus parent n'est-elle pas inutile?

Linux utilise une méthode de création d'un nouveau processus en copiant les ressources du processus parent vers l'enfant, mais normalement le processus enfant utilise un appel système tel que execve pour traiter un espace d'adressage différent de celui du processus parent. Ce processus de duplication semble inefficace, comme vous le faites souvent.

Pour résoudre ces problèmes, les deux processus partagent la même page physique et partagent la structure de données du noyau par processus, telle que la copie sur écriture, qui alloue une nouvelle page uniquement lorsque l'une d'elles change, et un descripteur de fichier ouvert. Nous essayons d'améliorer l'efficacité par de telles méthodes.

La prochaine fois, lorsque j'écrirai sur l'introduction à l'API Socket pour apprendre en langage C, je ferai de la programmation en utilisant plusieurs processus.

Recommended Posts

Pirater les appels système de Linux
[Linux] [Paramètres initiaux] Paramètres système
Pirater un descripteur de fichier Linux
Architecture du système Linux [niveau d'exécution]
J'ai essayé d'ajouter des appels système et des planificateurs à Linux
Périphérique et système de fichiers Linux
Comprendre l'audit du système d'audit Linux
Système de gestion de paquet principal Linux