Identyfikatory procesów i wątków w Linuksie

Bolesław Kulbabiński

Do obsługi wątków w jądrze od wersji 2.6 stosuje się implementację NPTL (Native POSIX Thread Library). Zgodnie ze standardem POSIX (man pthreads) z punktu widzenia użytkownika istnieje rozróżnienie pomiędzy procesem a wątkiem - jeden proces wykonujący program może składać się z wielu wątków. Każdy proces posiada swój unikatowy numer PID (Process IDentifier), który jest jednak wspólny dla wszystkich jego wątków. Wątki z kolei posiadają unikatowe numery TID (Thread IDentifier). Funkcjonuje również pojęcie grup wątków posiadających identyfikatory TGID (Thread Group IDentifier). W NPTL wszystkie wątki należące do jednego procesu należą też do tej samej grupy wątków.

Numery PID i TID można uzyskać za pomocą funkcji systemowych getpid() i gettid():


  #include <stdio.h>
  #include <sys/syscall.h>
  #include <unistd.h>
  #include <pthread.h>

  void* thread(void *arg){
      printf("child thread: getpid = %ld gettid = %ld\n", syscall(SYS_getpid), syscall(SYS_gettid));
      sleep(20);
      return NULL;
  }

  int main(){
      pthread_t threads[2];

      printf("main thread:  getpid = %ld gettid = %ld\n", syscall(SYS_getpid), syscall(SYS_gettid));

      pthread_create(&threads[0], NULL, thread, NULL);
      pthread_create(&threads[1], NULL, thread, NULL);
      pthread_join(threads[0], NULL);
      pthread_join(threads[1], NULL);

      return 0;
  }

Powyższy program wypisze na przykład:


  main thread:  getpid = 6482 gettid = 6482
  child thread: getpid = 6482 gettid = 6483
  child thread: getpid = 6482 gettid = 6484

Wywołanie pstree -p również pokaże numerki TID:


  └─thr(6482)─┬─{thr}(6483)
              └─{thr}(6484)

Z drugiej strony, z punktu widzenia jądra Linuksa, zamiast mówić o procesach i wątkach mówi się o zadaniach, czyli "obiektach" reprezentowanych przez metryczkę procesu, czyli strukturę task_struct. Spośród wyżej wymienionych identyfikatorów, w strukturze task_struct znajdują się tylko dwa:


  pid_t pid;
  pid_t tgid;

Gdzie zatem zapisane są TID-y wątków? Okazuje się, że pole pid z task_struct nie jest tym samym co PID opisywany w standardzie POSIX, lecz unikatowym identyfikatorem każdego zadania w systemie. Nadaje się tym samym na bycie TID-em i wygląda na to, że tak w istocie jest.

Oto kod prostego modułu jądra, który iteruje po wszystkich procesach w systemie i ich procesach potomnych, oraz wyciąga ich pola pid i tgid:


  #include <linux/module.h>
  #include <linux/sched.h>
  #include <linux/smp_lock.h>

  int init_module(void){
      struct list_head *i;
      struct task_struct *task, *act;

      lock_kernel();
      for_each_process(task){
          printk(KERN_CRIT "name: %s, pid: %d, tgid: %d\n", task->comm, task->pid, task->tgid);
          list_for_each(i, &task->children){
              act = (struct task_struct*) list_entry(i, struct task_struct, sibling);
              printk(KERN_CRIT "name: %s, pid: %d, tgid: %d\n", act->comm, act->pid, act->tgid);
          }
      }
      unlock_kernel();
      return 0;
  }

  void cleanup_module(void){
  }

Załadowanie powyższego modułu podczas gdy w tle uruchomiony jest program podany na górze strony (o nazwie thr) powoduje wypisanie między innymi:


  name: thr, pid: 6482, tgid: 6482
  name: thr, pid: 6483, tgid: 6482
  name: thr, pid: 6484, tgid: 6482

Pole pid pokrywa się z tym, co użytkownik może zobaczyć wywołując gettid(), natomiast pole tgid z tym co zwraca getpid().

Identyfikatory odczytane z task_struct są również zgodne z tym co można odczytać z /proc:


  # ls /proc/6482/task
  6482 6483 6484

  # cat /proc/6482/task/6482/status
  Name:   thr
  State:  S (sleeping)
  Tgid:   6482
  Pid:    6482
  ...

  # cat /proc/6482/task/6483/status
  Name:   thr
  State:  S (sleeping)
  Tgid:   6482
  Pid:    6483
  ...

  # cat /proc/6482/task/6484/status
  Name:   thr
  State:  S (sleeping)
  Tgid:   6482
  Pid:    6484
  ...

(Programy uruchamiane były na jądrze 2.6.29.6)


Janina Mincer-Daszkiewicz