Linux kernel hacking: contenitori di processi/1

Dalla versione 2.6.24, il kernel Linux mette a disposizione un framework denominato Control Groups (o cgroups) che permette di creare veri e propri contenitori di processi.

Ad ogni contenitore possono essere associate determinate configurazioni che permettono di definire ad esempio il tracciamento e/o il controllo sull’utilizzo di determinate risorse.

L’infrastruttura dei cgroup mette a disposizione solo le funzionalità di raggruppamento dei task, sono poi i vari cgroup subsystem che implementano le particolari politiche di controllo per ciascuna risorsa: come ad esempio la CPU, la banda di I/O, la memoria, i permessi di accesso a determinati device, eccetera.

Le potenzialità di questa infrastruttura non sono affatto trascurabili. Ad esempio, consideriamo un server condiviso da varie classi di utenti: guest, power-user e sysadmin. I guest potrebbero essere autorizzati a non consumare più del 40% della CPU, i power-user il 50% e potremmo voler riservare il restante 10% agli amministratori per avere la garanzia di poter eseguire in qualsiasi momento operazioni di emergenza, indipendentemente da quanta CPU consumano i guest o i power-user.

Questo particolare “use case” non sarebbe realizzabile senza un framework che permetta di definire raggruppamenti a livello di processi che vada oltre al concetto di utente e gruppo UNIX.

I cgroup, invece, permettono di implementare tali politiche creando prima un contenitore per ciascuna classe di utenti, e configurando poi i particolari cgroup subsystem per quanto riguarda le regole di controllo.

Una volta confinate le classi di utenti nei rispettivi contenitori, i vari processi (vecchi e nuovi) si troveranno ad utilizzare un set ristretto di risorse opportunamente controllate dai cgroup subsystem.

Se pensiamo di avere un cgroup subsystem che controlli ogni possibile risorsa, avremo realizzato un vero e proprio ambiente di Virtual Private Server (VPS), utilizzando le funzionalità messe a disposizione di un qualsiasi kernel vanilla.

Utilizzo dei cgroup

L’amministrazione dei cgroup viene effettuata per mezzo di uno special virtual filesystem (una sorta di procfs o sysfs), denominato banalmente cgroupfs.

Facciamo un esempio pratico utilizzando un kernel 2.6.28 (es. il kernel distribuito con Ubuntu 9.04).

Per prima cosa montiamo il cgroup filesystem:

$ sudo mount -t cgroup none /mnt

È possibile anche montare solo determinati cgroup subsystem aggiungendo l’opzione -o nome_cgroup al comando mount.

Tipicamente un cgroup subsystem è identificato da vari file nella forma nome_cgroup.parametro.

Vediamo dal contenuto del filesystem che di default sono disponibili vari cgroup subsystem:

$ ls /mnt
cpuacct.usage cpuset.sched_load_balance
cpu.rt_period_us cpuset.sched_relax_domain_level
cpu.rt_runtime_us cpu.shares
cpuset.cpu_exclusive memory.failcnt
cpuset.cpus memory.force_empty
cpuset.mem_exclusive memory.limit_in_bytes
cpuset.mem_hardwall memory.max_usage_in_bytes
cpuset.memory_migrate memory.stat
cpuset.memory_pressure memory.usage_in_bytes
cpuset.memory_pressure_enabled notify_on_release
cpuset.memory_spread_page release_agent
cpuset.memory_spread_slab tasks
cpuset.mems

Nell’esempio specifico cpu, cpuacct, cpuset e memory sono i vari cgroup subsystem che possono essere utilizzati:

  • cpu: controllo della CPU pilotato dal Completely Fair Scheduler (CFS);
  • cpuacct: semplice accounting dell’utilizzo della CPU per cgroup;
  • cpuset: assegnamento completo di CPU e memory node a determinati cgroup
    (utilizzato tipicamente su sistemi composti da un elevato numero di processori o grandi sistemi NUMA;
  • memory: riserva porzioni di memoria a determinati cgroup.

Per approfondire le funzionalità di ciascun controller consultate la documentazione del kernel nei file Documentation/cgroups/* (disponibili direttamente nei sorgenti del kernel).

I parametri che si trovano nella root di questo filesystem fanno riferimento al cgroup principale, dove inizialmente sono contenuti tutti i processi che girano nel sistema.

È possibile visualizzare la lista dei PID contenuti in un determinato cgroup leggendo il contenuto del file tasks:

$ cat /mnt/tasks
1
2
3
...
6499
6507
7155

Inizialmente, senza nessuna particolare configurazione, tutti i processi girano all’interno del root cgroup.

Come semplice esempio rimontiamo di nuovo il cgroup filesystem utilizzando solo il cgroup subsystem “cpu”.

$ sudo mount -t cgroup -o cpu,remount none /mnt/

Per creare un nuovo contenitore è sufficiente creare una directory all’interno del cgroup filesystem (banalmente con un mkdir).

Tutti i file presenti nella root del cgroup filesystem vengono replicati all’interno di ciascuna directory che identifica un particolare cgroup.

Ad esempio creiamo due nuovi contenitori: “multimedia” e “browser”. Come utente root eseguiamo i comandi:

$ sudo mkdir /mnt/multimedia
$ sudo mkdir /mnt/browser

Nel primo cgroup vogliamo eseguire applicazioni multimediali, tipicamente CPU bound (es. un player mp3, o video), e vogliamo garantire che risponano sempre entro certi limiti di tempo, per non perdere nemmeno un frame o sentire i fastidiosi “salti” anche quando il resto del sistema è sovraccarico.

Per questo motivo configuriamo il cgroup multimedia per ricevere il doppio della banda di CPU rispetto al cgroup browser:

$ echo 2048 > /mnt/multimedia/cpu.shares
$ echo 1024 > /mnt/browser/cpu.shares

Per mettere un processo (e tutti i futuri figli) dentro un contenitore basta scrivere il PID del processo in questione nel file tasks, all’interno del cgroup in cui vogliamo muoverlo.

Nel nostro esempio possiamo voler eseguire una istanza di firefox nel cgroup browser e una istanza di mplayer nel cgroup multimedia. Ad esempio:

$ firefox &
$ echo $! > /mnt/browser/tasks

$ mplayer music.mp3 &
$ echo $! > /mnt/browser/tasks

Oltre a ciò lo scheduler CFS implementa anche delle politiche per effettuare uno scheduling equo (fair scheduling) tra cgroup.

Normalmente lo scheduler tenta di effettuare una distribuzione equa della banda di CPU per task, ma nel contesto dei cgroup questa politica può non essere la scelta più opportuna. Prendendo ad esempio il caso sopra, se lo scheduler ragionasse per task un cgroup potrebbe accrescere la propria quota di CPU semplicemente aumentando il numero di task al suo interno.

La logica di equità per task può essere spostata sui cgroup, abilitando le seguenti opzioni in fase di compilazione del kernel:

 CONFIG_FAIR_GROUP_SCHED=y
 CONFIG_CGROUP_SCHED=y

In questo modo CFS utilizza una logica a due livelli: prima distribuisce equamente la banda di CPU tra cgroup e poi tra i singoli task all’interno di ciascun cgroup.

Un esempio pratico

Adesso che conosciamo le nozioni di base dei cgroup verifichiamone l’efficacia con un piccolo esempio pratico.

Prendiamo la configurazione che abbiamo visto prima, e proviamo a far girare delle applicazioni all’interno dei cgroup che abbiamo creato (“multimedia” e “browser”).

Prendiamo in esame l’applicazione riportata di seguito come simulatore ad-hoc del nostro workload CPU-bound.

Applicazione #1: cpuhog.c

/*
 *  cpuhog
 *
 *  Copyright (C) 2007 Andrea Righi 
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
USA
 *
 *  Compile with:
 *
 *    gcc -Wall -o cpuhog cpuhog.c
 */

#define _GNU_SOURCE
#define __USE_GNU

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define WAIT_NICE       19

volatile int spin = 0;
pid_t *pids;

static void cpu_hog(void)
{
       unsigned char data[64], i = 0;

       while (1)
               data[i++ % sizeof(data)]++;
}

static void sig_hup(int sig)
{
       ++spin;
       signal(SIGHUP, sig_hup);
}

int main(int argc, char **argv)
{
       unsigned int tasks, time, i;
       cpu_set_t cmask;

       /* parse cmdline arguments */
       if (argc < 3) {
               fprintf(stderr, "usage: cpuhog NTASKS TIME\n");
               exit(1);
       }
       tasks = atoi(argv[1]);
       if (tasks > 1024) {
               fprintf(stderr, "too many tasks\n");
               exit(1);
       }
       time = atoi(argv[2]);

       pids = calloc(1, tasks * sizeof(pid_t));
       if (!pids) {
               perror("calloc");
               exit(1);
       }

       /* lock all memory (avoid swap) */
       mlockall(MCL_CURRENT | MCL_FUTURE);

       signal(SIGHUP, sig_hup);
       CPU_ZERO(&cmask);
       CPU_SET(0, &cmask);

       /* spawn tasks */
       for (i = 0; i < tasks; i++) {
               pids[i] = fork();
               if (pids[i] == -1) {
                       perror("fork");
                       return 1;
               }
               if (!pids[i]) {
                       /* bind all children to CPU0 */
                       if (sched_setaffinity(getpid(), sizeof(cmask),
                                               &cmask) < 0) {
                               perror("sched_setaffinity");
                               exit(1);
                       }
                       signal(SIGHUP, sig_hup);
                       nice(WAIT_NICE);
                       while (!spin);
                       nice(0);
                       cpu_hog();
                       exit(0);
               }
       }
       /* start all tasks */
       for (i = 0; i < tasks; i++) {
               fprintf(stdout, "%u\n", pids[i]);
               if (kill(pids[i], SIGHUP))
                       perror("SIGHUP");
               fflush(stdout);
       }

       /* wait... */
       sleep(time);

       /* kill all tasks and stop hogging the cpu */
       for (i = 0; i < tasks; i++)
               if (kill(pids[i], SIGKILL))
                       perror("SIGKILL");
       return 0;
}

L'applicazione cpuhog.c accetta due argomenti: il numero di task da creare e il tempo di durata del run in secondi. I task creati consumano semplicemente banda sulla CPU0.

Compiliamo cpuhog con:

$ gcc -Wall -o cpuhog cpuhog.c

Possiamo definire due filename diversi utilizzando lo stesso binario per facilitare l'analisi sull'occupazione di CPU dal comando top durante il run dei test.

$ ln -s cpuhog cpuhog-multimedia
$ ln -s cpuhog cpuhog-browser

Inizialmente lanciamo due istanze di cpuhog nello stesso cgroup (quello principale). Apriamo quindi due shell: shell-1 e shell-2.

Nella shell-1 lanciamo un cpuhog con 10 task per un tempo totale di run di 30sec:

shell-2 $ echo $$
24794
shell-2 $ cat /proc/$$/cgroup
4:cpu:/
shell-2 $ ./cpuhog-multimedia 10 30

Nella shell-2 lanciamo un cpuhog con 5 task paralleli della durata di 30sec:

shell-2 $ echo $$
24358
shell-2 $ cat /proc/$$/cgroup
4:cpu:/
shell-2 $ ./cpuhog-browser 5 30

Tramite il comando top ecco cosa vediamo:

%CPU %MEM    TIME+  COMMAND
  7  0.0   0:00.82 cpuhog-multimedia
  7  0.0   0:00.80 cpuhog-multimedia
  7  0.0   0:00.86 cpuhog-browser
  7  0.0   0:00.88 cpuhog-browser
  7  0.0   0:00.86 cpuhog-browser
  7  0.0   0:00.89 cpuhog-browser
  7  0.0   0:00.81 cpuhog-multimedia
  7  0.0   0:00.82 cpuhog-multimedia
  7  0.0   0:00.78 cpuhog-multimedia
  7  0.0   0:00.80 cpuhog-multimedia
  7  0.0   0:00.81 cpuhog-multimedia
  7  0.0   0:00.82 cpuhog-multimedia
  6  0.0   0:00.87 cpuhog-browser
  6  0.0   0:00.80 cpuhog-multimedia
  6  0.0   0:00.81 cpuhog-multimedia

In pratica tutti i processi a regime si prendono equamente il 6-7% della CPU0. Senza i cgroup l'applicazione multimedia sta in pratica utilizzando il 60-70% della CPU, mentre l'applicazione browser ne sta utilizzando solo il 30-40%. In realtà noi avremmo voluto assegnare la stessa quota di CPU a ciascuna applicazione, ma il diverso numero di task ne sta sbilanciando l'occupazione. Di fatto c'è equità a livello di task, ma non c'è equità a livello di applicazione.

Adesso ripetiamo lo stesso test lanciando le due istanze di cpuhog in due cgroup distinti.

Per prima cosa muoviamo shell-1 nel contenitore "multimedia" e resettiamo il numero di cpu.shares al default (1024):

shell-1 $ echo $$
24794
shell-1 $ echo 24794 | sudo tee /mnt/multimedia/tasks
shell-1 $ echo 1024 | sudo tee /mnt/multimedia/cpu.shares
shell-1 $ cat /proc/$$/cgroup
4:cpu:/multimedia

Facciamo lo stesso con la shell-2 e il contenitore "browser":

shell-2 $ echo $$
24358
shell-2 $ cat /proc/$$/cgroup
shell-2 $ echo 24358 | sudo tee /mnt/browser/tasks
shell-2 $ echo 1024 | sudo tee /mnt/browser/cpu.shares
4:cpu:/browser

E lanciamo di nuovo le due applicazioni:

shell-1 $ ./cpuhog-multimedia 10 30
shell-2 $ ./cpuhog-browser 5 30

Dall'output del comando top:

%CPU %MEM    TIME+  COMMAND
 10  0.0   0:00.51 cpuhog-browser
 10  0.0   0:00.50 cpuhog-browser
 10  0.0   0:00.51 cpuhog-browser
 10  0.0   0:00.50 cpuhog-browser
 10  0.0   0:00.50 cpuhog-browser
  5  0.0   0:00.24 cpuhog-multimedia
  5  0.0   0:00.24 cpuhog-multimedia
  5  0.0   0:00.24 cpuhog-multimedia
  5  0.0   0:00.23 cpuhog-multimedia
  5  0.0   0:00.23 cpuhog-multimedia
  5  0.0   0:00.22 cpuhog-multimedia
  5  0.0   0:00.23 cpuhog-multimedia
  5  0.0   0:00.23 cpuhog-multimedia
  5  0.0   0:00.23 cpuhog-multimedia
  5  0.0   0:00.22 cpuhog-multimedia

Decisamente meglio! Adesso entrambe le applicazioni utilizzano esattamente il 50% della CPU, indipendentemente dal numero di task che esse generano.

Realizzazione di un semplice cgroup subsystem

Vediamo adesso l'aspetto di programmazione implementandoci un nostro semplice cgroup subsystem all'interno del kernel.

Partiamo con il cgroup subsystem più semplice che ci sia, cioè quello che non fa ehm... niente, ma usa l'infrastruttura dei cgroup come classificatore di insiemi di task. Chiameremo questo cgroup subsystem il noop-cgroup.

Tale funzionalità in fin dei conti non è del tutto inutile, ad esempio può essere utilizzata per assegnare un "nickname" ad un insieme di PID ed identificarli velocemente con un semplice "cat /mnt/nickname/tasks".

Procediamo quindi alla modifica del kernel per implementare il nostro noop-cgroup subsystem.

Per prima cosa scarichiamoci il tarball dell'ultimo kernel disponibile su http://www.kernel.org, ed estraiamo i sorgenti (per chi si sente già un vero kernel developer può prelevare i sorgenti anche direttamente dal repository git di Linus; per questo consultare l'howto al link http://linux.yyz.us/git-howto.html).

Una volta che abbiamo a disposizione la directory dei sorgenti andiamo a modificare/creare i seguenti file:

  1. init/Kconfig: parametri di configurazione del kernel
  2. include/linux/cgroup_subsys.h: definizione dei cgroup subsystem
  3. kernel/cgroup_noop.c: implementazione del noop cgroup subsystem
  4. kernel/Makefile: il Makefile relativo ai componenti "core" del kernel

Di seguito vengono riportate le modifiche sotto forma di patch, corredate da commenti. Alla fine dell'articolo è possibile trovare la patch completa. Tale patch può essere applicata direttamente ai sorgenti del kernel vanilla utilizzando il comando:

$ patch -p1 < cgroup-noop.patch

Procediamo adesso con il commento passo passo della patch.

1) Inizialmente definiamo il nuovo cgroup subsystem come opzionale; questo permette di decidere in fase di compilazione del kernel se mettere a disposizione o meno il nostro sottosistema noop-cgroup.

—- a/init/Kconfig
+++ b/init/Kconfig
@@ -528,6 +528,12 @@ config CGROUP_FREEZER
         Provides a way to freeze and unfreeze all tasks in a
         cgroup.

+config CGROUP_NOOP
+       bool "noop cgroup subsystem"
+       depends on CGROUPS
+       help
+         This provides cgroup subsystem which has no special features.
+
 config CGROUP_DEVICE
       bool "Device controller for cgroups"
       depends on CGROUPS && EXPERIMENTAL

2) La dichiarazione del nuovo cgroup subsystem si limita all'utilizzo della macro SUBSYS(), messa a disposizione dall'infrastruttura dei cgroup:

—- a/include/linux/cgroup_subsys.h
+++ b/include/linux/cgroup_subsys.h
@@ -59,4 +59,8 @@ SUBSYS(freezer)
 SUBSYS(net_cls)
 #endif

+#ifdef CONFIG_CGROUP_NOOP
+SUBSYS(noop)
+#endif
+
 /* */

3) Passiamo finalmente al codice vero e proprio, quello che implementa il cuore del subsystem noop-cgroup:

—- /dev/null
+++ b/kernel/cgroup_noop.c
@@ -0,0 +1,67 @@
+#include 
+#include 
+#include 
+
+/*
+ * La struttura base dei cgroup che utilizzano il sottosistema noop,
+ */
+struct noop_cgroup {
+       struct cgroup_subsys_state css;
+};
+
+/*
+ * Restituisce la struct noop_cgroup corrispondente ad una struct
cgroup
+ * generica.
+ */
+static struct noop_cgroup *noop_cgroup_from_cgroup(struct cgroup *cgrp)
+{
+       return container_of(cgroup_subsys_state(cgrp, noop_subsys_id),
+                       struct noop_cgroup, css);
+}
+
+/*
+ * Routine invocata tutte le volte che si crea un nuovo cgroup nel
cgroupfs
+ * (con mkdir).
+ *
+ * Nel nostro caso vengono inizializzati semplicemente i "metadati" del
cgroup.
+ */
+static struct cgroup_subsys_state *
+noop_create(struct cgroup_subsys *ss, struct cgroup *cgrp)
+{
+       struct noop_cgroup *nocg;
+
+       nocg = kzalloc(sizeof(*nocg), GFP_KERNEL);
+       if (unlikely(!nocg))
+               return ERR_PTR(-ENOMEM);
+       return &nocg->css;
+}
+
+/*
+ * Routine invocata tutte le volte che si rimuove un nuovo cgroup nel
cgroupfs
+ * (con rmdir).
+ *
+ * Tale funzione si limita a deallocare la struct noop_cgroup
precedentemente
+ * allocata con noop_create().
+ */
+static void noop_destroy(struct cgroup_subsys *ss, struct cgroup *cgrp)
+{
+       struct noop_cgroup *nocg = noop_cgroup_from_cgroup(cgrp);
+
+       kfree(nocg);
+}
+
+/*
+ * Struttura principale del cgroup subsystem "noop".
+ *
+ * Essendo un cgroup estremamente minimale include solo le funzioni di
+ * creazione e rimozione di cgroup.
+ */
+struct cgroup_subsys noop_subsys = {
+       /* proprietà */
+       .name = "noop",
+       .subsys_id = noop_subsys_id,
+       .early_init = 0,
+       /* metodi */
+       .create = noop_create,
+       .destroy = noop_destroy,
+};

4) Infine aggiungiamo al Makefile opportuno il nuovo file da compilare, in funzione dell'opzione CONFIG_CGROUP_NOOP:

—- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_CGROUP_DEBUG) += cgroup_debug.o
 obj-$(CONFIG_CGROUP_FREEZER) += cgroup_freezer.o
 obj-$(CONFIG_CPUSETS) += cpuset.o
 obj-$(CONFIG_CGROUP_NS) += ns_cgroup.o
+obj-$(CONFIG_CGROUP_NOOP) += cgroup_noop.o
 obj-$(CONFIG_UTS_NS) += utsname.o
 obj-$(CONFIG_USER_NS) += user_namespace.o
 obj-$(CONFIG_PID_NS) += pid_namespace.o

NOTA: possiamo trovare una implementazione ottimizzata e decisamente meno verbosa del noop-cgroup al link http://lwn.net/Articles/314238/.

Il cgroup subsystem noop in azione

Disclaimer: utilizzare un proprio kernel ricompilato è sempre un'operazione piuttosto rischiosa, se si fanno degli errori si possono generare fastidiosi freeze di sistema o comprometterne addirittura l'utilizzo. Per questo motivo si consiglia di usare una tecnologia di virtualizzazione, ad esempio Qemu o KVM (anche l'autore ne fa ampio uso) prima di passare ad ambienti di "produzione" veri e propri.

Procediamo adesso con la ricompilazione del kernel per vedere il nostro noop-cgroup in azione.

Il primo passo da fare è abilitarne la compilazione. Per questo è necessario lanciare un make menuconfig o make xconfig e nella sezione "Control Group support" spuntare il "noop cgroup subsystem". Salvare ed uscire per attivare la nuova configurazione del kernel (.config).

A questo punto è possibile ricompilare il kernel. Per questo passo si trovano tantissimi howto in rete (ad es. http://www.cyberciti.biz/tips/compiling-linux-kernel-26.html), quindi non ci dilungheremo su ulteriori dettagli.

Una volta fatto il reboot con il nuovo kernel ricompilato è possibile sperimentare un possibile utilizzo del nostro cgroup subsystem.

Montiamo quindi il cgroupfs abilitando esclusivamente il noop-cgroup:

$ sudo mount -cgroup -onoop /mnt

Creiamo un cgroup fittizio:

$ sudo mkdir /mnt/mysession

Spostiamo la shell corrente dentro il cgroup "mysession":

$ echo $$ | sudo tee /mnt/mysession/tasks

Adesso possiamo provare a forkare un gran numero di processi in background (diciamo 100):

$ for i in `seq 1 100`; do sleep 1000 & done

Se ad un certo punto volessi terminare solo ed esclusivamente questa mia sessione (ma non eventuali altre sessioni) basterebbe un semplice comando:

$ kill `cat /mnt/mysession/tasks`

Il comando in questione "killa" sia la shell iniziale, sia tutti i task che questa ha "forkato" in background, fornendo un metodo semplice e veloce per la loro identificazione.

Abbiamo cioè utilizzato la funzionalità base messa a disposizione dall'infrastruttura dei cgroup per "taggare" tutti i task afferenti ad una sessione di terminale, raggruppandoli in un cgroup comune per poi terminarli facilmente.

Un altro utilizzo interessante potrebbe essere quello di tenere traccia dell'inizio e della fine di "sotto-sessioni" come quella riportata sopra, controllando quando il file tasks diventa vuoto (vedere anche la documentazione per i file /mnt/release_agent e i file notify_on_release in ciascun cgroup).

Conclusioni

Abbiamo visto che le potenzialità dei Linux cgroup sono veramente enormi.

Anche con poche righe di codice è possibile sfruttare il concetto di raggruppamento di processi per mettere a disposizione dello user-space funzionalità decisamente utili.

Nel prossimo articolo vedremo come sia possibile estendere la struttura del noop-cgroup per attuare policy di filtraggio di determinate operazioni per cgroup (es. le system call).

cgroup-noop.patch (versione completa)

 include/linux/cgroup_subsys.h |    4 ++
 init/Kconfig                  |    6 ++++
 kernel/Makefile               |    1 +
 kernel/cgroup_noop.c          |   67
+++++++++++++++++++++++++++++++++++++++++
 4 files changed, 78 insertions(+), 0 deletions(-)

diff —git a/include/linux/cgroup_subsys.h
b/include/linux/cgroup_subsys.h
index 9c8d31b..a9bed4f 100644
—- a/include/linux/cgroup_subsys.h
+++ b/include/linux/cgroup_subsys.h
@@ -59,4 +59,8 @@ SUBSYS(freezer)
 SUBSYS(net_cls)
 #endif

+#ifdef CONFIG_CGROUP_NOOP
+SUBSYS(noop)
+#endif
+
 /* */
diff —git a/init/Kconfig b/init/Kconfig
index 7be4d38..beb49c0 100644
—- a/init/Kconfig
+++ b/init/Kconfig
@@ -528,6 +528,12 @@ config CGROUP_FREEZER
         Provides a way to freeze and unfreeze all tasks in a
         cgroup.

+config CGROUP_NOOP
+       bool "noop cgroup subsystem"
+       depends on CGROUPS
+       help
+         This provides cgroup subsystem which has no special features.
+
 config CGROUP_DEVICE
       bool "Device controller for cgroups"
       depends on CGROUPS && EXPERIMENTAL
diff —git a/kernel/Makefile b/kernel/Makefile
index 4242366..70aaf99 100644
—- a/kernel/Makefile
+++ b/kernel/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_CGROUP_DEBUG) += cgroup_debug.o
 obj-$(CONFIG_CGROUP_FREEZER) += cgroup_freezer.o
 obj-$(CONFIG_CPUSETS) += cpuset.o
 obj-$(CONFIG_CGROUP_NS) += ns_cgroup.o
+obj-$(CONFIG_CGROUP_NOOP) += cgroup_noop.o
 obj-$(CONFIG_UTS_NS) += utsname.o
 obj-$(CONFIG_USER_NS) += user_namespace.o
 obj-$(CONFIG_PID_NS) += pid_namespace.o
diff —git a/kernel/cgroup_noop.c b/kernel/cgroup_noop.c
new file mode 100644
index 0000000..cc3fe89
—- /dev/null
+++ b/kernel/cgroup_noop.c
@@ -0,0 +1,67 @@
+#include 
+#include 
+#include 
+
+/*
+ * La struttura base dei cgroup che utilizzano il sottosistema noop,
+ */
+struct noop_cgroup {
+       struct cgroup_subsys_state css;
+};
+
+/*
+ * Restituisce la struct noop_cgroup corrispondente ad una struct
cgroup
+ * generica.
+ */
+static struct noop_cgroup *noop_cgroup_from_cgroup(struct cgroup *cgrp)
+{
+       return container_of(cgroup_subsys_state(cgrp, noop_subsys_id),
+                       struct noop_cgroup, css);
+}
+
+/*
+ * Routine invocata tutte le volte che si crea un nuovo cgroup nel
cgroupfs
+ * (con mkdir).
+ *
+ * Nel nostro caso vengono inizializzati semplicemente i "metadati" del
cgroup.
+ */
+static struct cgroup_subsys_state *
+noop_create(struct cgroup_subsys *ss, struct cgroup *cgrp)
+{
+       struct noop_cgroup *nocg;
+
+       nocg = kzalloc(sizeof(*nocg), GFP_KERNEL);
+       if (unlikely(!nocg))
+               return ERR_PTR(-ENOMEM);
+       return &nocg->css;
+}
+
+/*
+ * Routine invocata tutte le volte che si rimuove un nuovo cgroup nel
cgroupfs
+ * (con rmdir).
+ *
+ * Tale funzione si limita a deallocare la struct noop_cgroup
precedentemente
+ * allocata con noop_create().
+ */
+static void noop_destroy(struct cgroup_subsys *ss, struct cgroup *cgrp)
+{
+       struct noop_cgroup *nocg = noop_cgroup_from_cgroup(cgrp);
+
+       kfree(nocg);
+}
+
+/*
+ * Struttura principale del cgroup subsystem "noop".
+ *
+ * Essendo un cgroup estremamente minimale include solo le funzioni di
+ * creazione e rimozione di cgroup.
+ */
+struct cgroup_subsys noop_subsys = {
+       /* proprietà */
+       .name = "noop",
+       .subsys_id = noop_subsys_id,
+       .early_init = 0,
+       /* metodi */
+       .create = noop_create,
+       .destroy = noop_destroy,
+};

Comments

  1. Molto molto bello come post!

    L’approccio seguito dai cgroups mi pare molto elegante e pratico.

    Resto in attesa di poter vedere la seconda parte. 🙂

    Ciao!

  2. Veramente interessante, Andrea.

Policy per i commenti: Apprezzo moltissimo i vostri commenti, critiche incluse. Per evitare spam e troll, e far rimanere il discorso civile, i commenti sono moderati e prontamente approvati poco dopo il loro invio.