Rumah > Artikel > Operasi dan penyelenggaraan > Mari analisa nombor ID proses teknik klasik Linux
Artikel ini membawa anda pengetahuan yang berkaitan tentang analisis nombor ID proses dalam Linux proses Linux sentiasa menetapkan nombor untuk mengenal pasti mereka secara unik dalam ruang nama mereka. Nombor ini dipanggil nombor ID proses, atau singkatannya PID Mari kita lihat isu berkaitan saya harap ia akan membantu semua orang.
Kod dalam artikel ini dipetik daripada kernel Linux versi 5.15.13.
Proses Linux sentiasa diberikan nombor yang mengenal pasti mereka secara unik dalam ruang nama mereka. Nombor ini dipanggil nombor ID proses, atau singkatannya PID. Setiap proses yang dihasilkan oleh garpu atau klon secara automatik diberikan nilai PID unik baharu oleh kernel.
Selain nilai ciri PID, setiap proses juga mempunyai ID lain. Terdapat jenis kemungkinan berikut
1. Dalam kumpulan benang (dalam proses, gunakan bendera CLONE_THREAD untuk memanggil konteks pelaksanaan yang berbeza bagi proses yang dicipta oleh klon, seperti yang akan kita lihat kemudian) Semua proses dalam mempunyai ID kumpulan benang bersatu (TGID). Jika proses tidak menggunakan benang, PID dan TGIDnya adalah sama. Proses utama dalam kumpulan benang dipanggil ketua kumpulan. Ahli group_leader dari task_struct bagi semua thread yang dibuat melalui klon akan menunjuk ke contoh task_struct ketua kumpulan.
2. Selain itu, proses bebas boleh digabungkan ke dalam kumpulan proses (menggunakan panggilan sistem setpgrp). Nilai atribut pgrp bagi task_struct ahli kumpulan proses adalah sama, iaitu, PID ketua kumpulan proses. Kumpulan proses memudahkan penghantaran isyarat kepada semua ahli kumpulan, yang berguna untuk pelbagai aplikasi pengaturcaraan sistem (lihat literatur pengaturcaraan sistem, contohnya [SR05]). Ambil perhatian bahawa proses berpaip dimasukkan dalam kumpulan proses yang sama.
3. Beberapa kumpulan proses boleh digabungkan menjadi satu sesi. Semua proses dalam sesi mempunyai ID sesi yang sama, yang disimpan dalam ahli sesi task_struct. SID boleh ditetapkan menggunakan panggilan sistem setsid. Ia boleh digunakan untuk pengaturcaraan terminal.
Ruang nama meningkatkan kerumitan pengurusan PID. Ruang nama PID disusun secara hierarki. Apabila ruang nama baharu dicipta, semua PID dalam ruang nama kelihatan kepada ruang nama induk, tetapi ruang nama anak tidak dapat melihat PID ruang nama induk. Tetapi ini bermakna bahawa sesetengah proses mempunyai berbilang PID, dan di mana sahaja ruang nama proses itu boleh dilihat, ia akan diberikan PID. Ini mesti ditunjukkan dalam struktur data. Kita perlu membezakan antara ID tempatan dan ID global.
1. ID global ialah nombor ID unik dalam kernel itu sendiri dan ruang nama awal Proses init dimulakan semasa permulaan sistem tergolong dalam ruang nama awal. Untuk setiap jenis ID, terdapat ID global tertentu yang dijamin unik di seluruh sistem.
2. ID tempatan tergolong dalam ruang nama tertentu dan tidak mempunyai kesahihan global. Untuk setiap jenis ID, ia sah dalam ruang nama yang dimiliki, tetapi ID jenis dan nilai yang sama mungkin muncul dalam ruang nama yang berbeza.
PID global dan TGID disimpan secara langsung dalam task_struct, yang masing-masing merupakan ahli pid dan tgid task_struct, dalam fail sched.h :
struct task_struct {...pid_t pid;pid_t tgid;...}
Kedua-dua item ini adalah jenis pid_t, yang ditakrifkan sebagai __kernel_pid_t, yang ditakrifkan secara berasingan oleh setiap seni bina. Biasanya ditakrifkan sebagai int, 232 ID berbeza boleh digunakan pada masa yang sama.
Subsistem kecil yang dipanggil pengagih PID (pengumpuk pid) digunakan untuk mempercepatkan peruntukan ID baharu. Selain itu, kernel perlu menyediakan fungsi pembantu untuk melaksanakan fungsi mencari task_struct proses mengikut ID dan jenisnya, serta fungsi menukar perwakilan kernel ID dan nilai yang boleh dilihat oleh ruang pengguna.
Terdapat definisi berikut dalam fail pid_namespace.h:
struct pid_namespace { struct idr idr; struct rcu_head rcu; unsigned int pid_allocated; struct task_struct *child_reaper; struct kmem_cache *pid_cachep; unsigned int level; struct pid_namespace *parent;#ifdef CONFIG_BSD_PROCESS_ACCT struct fs_pin *bacct;#endif struct user_namespace *user_ns; struct ucounts *ucounts; int reboot; /* group exit code if this pidns was rebooted */ struct ns_common ns;} __randomize_layout;
Setiap ruang nama PID mempunyai proses, yang memainkannya peranan yang setara dengan proses init global. Satu tujuan init adalah untuk memanggil wait4 pada proses anak yatim, dan varian init ruang nama-tempatan juga mesti melakukan tugas ini. child_reaper menyimpan penunjuk ke task_struct proses.
Induk ialah penunjuk kepada ruang nama induk dan tahap mewakili kedalaman ruang nama semasa dalam hierarki ruang nama. Tahap ruang nama awal ialah 0, tahap subruang ruang nama ialah 1, tahap subruang lapisan seterusnya ialah 2, dan seterusnya. Pengiraan tahap adalah penting kerana ID dalam ruang nama dengan tahap yang lebih tinggi kelihatan kepada ruang nama dengan tahap yang lebih rendah. Daripada tetapan tahap tertentu, kernel boleh membuat kesimpulan berapa banyak ID proses yang akan dikaitkan dengannya.
Pengurusan PID berkisar pada dua pengembangan Struktur data: struct pid ialah perwakilan dalaman kernel bagi PID, manakala struct upid mewakili maklumat yang boleh dilihat dalam ruang nama tertentu. Takrifan dua struktur terdapat dalam fail pid.h, seperti berikut:
/* * What is struct pid? * * A struct pid is the kernel's internal notion of a process identifier. * It refers to inpidual tasks, process groups, and sessions. While * there are processes attached to it the struct pid lives in a hash * table, so it and then the processes that it refers to can be found * quickly from the numeric pid value. The attached processes may be * quickly accessed by following pointers from struct pid. * * Storing pid_t values in the kernel and referring to them later has a * problem. The process originally with that pid may have exited and the * pid allocator wrapped, and another process could have come along * and been assigned that pid. * * Referring to user space processes by holding a reference to struct * task_struct has a problem. When the user space process exits * the now useless task_struct is still kept. A task_struct plus a * stack consumes around 10K of low kernel memory. More precisely * this is THREAD_SIZE + sizeof(struct task_struct). By comparison * a struct pid is about 64 bytes. * * Holding a reference to struct pid solves both of these problems. * It is small so holding a reference does not consume a lot of * resources, and since a new struct pid is allocated when the numeric pid * value is reused (when pids wrap around) we don't mistakenly refer to new * processes. *//* * struct upid is used to get the id of the struct pid, as it is * seen in particular namespace. Later the struct pid is found with * find_pid_ns() using the int nr and struct pid_namespace *ns. */struct upid { int nr; struct pid_namespace *ns;};struct pid{ refcount_t count; unsigned int level; spinlock_t lock; /* lists of tasks that use this pid */ struct hlist_head tasks[PIDTYPE_MAX]; struct hlist_head inodes; /* wait queue for pidfd notifications */ wait_queue_head_t wait_pidfd; struct rcu_head rcu; struct upid numbers[1];};
对于struct upid, nr表示ID的数值, ns是指向该ID所属的命名空间的指针。所有的upid实例都保存在一个散列表中。 pid_chain用内核的标准方法实现了散列溢出链表。struct pid的定义首先是一个引用计数器count。 tasks是一个数组,每个数组项都是一个散列表头,对应于一个ID类型。这样做是必要的,因为一个ID可能用于几个进程。所有共享同一给定ID的task_struct实例,都通过该列表连接起来。 PIDTYPE_MAX表示ID类型的数目:
enum pid_type{ PIDTYPE_PID, PIDTYPE_TGID, PIDTYPE_PGID, PIDTYPE_SID, PIDTYPE_MAX,};
一个进程可能在多个命名空间中可见,而其在各个命名空间中的局部ID各不相同。 level表示可以看到该进程的命名空间的数目(换言之,即包含该进程的命名空间在命名空间层次结构中的深度),而numbers是一个upid实例的数组,每个数组项都对应于一个命名空间。注意该数组形式上只有一个数组项,如果一个进程只包含在全局命名空间中,那么确实如此。由于该数组位于结构的末尾,因此只要分配更多的内存空间,即可向数组添加附加的项。
由于所有共享同一ID的task_struct实例都按进程存储在一个散列表中,因此需要在struct task_struct中增加一个散列表元素在sched.h文件内进程的结构头定义内有
struct task_struct {... /* PID/PID hash table linkage. */ struct pid *thread_pid; struct hlist_node pid_links[PIDTYPE_MAX]; struct list_head thread_group; struct list_head thread_node;...};
将task_struct连接到表头在pid_links中的散列表上。
假如已经分配了struct pid的一个新实例,并设置用于给定的ID类型。它会如下附加到task_struct,在kernel/pid.c文件内:
static struct pid **task_pid_ptr(struct task_struct *task, enum pid_type type){ return (type == PIDTYPE_PID) ? &task->thread_pid : &task->signal->pids[type];}/* * attach_pid() must be called with the tasklist_lock write-held. */void attach_pid(struct task_struct *task, enum pid_type type){ struct pid *pid = *task_pid_ptr(task, type); hlist_add_head_rcu(&task->pid_links[type], &pid->tasks[type]);}
这里建立了双向连接: task_struct可以通过task_struct->pids[type]->pid访问pid实例。而从pid实例开始,可以遍历tasks[type]散列表找到task_struct。 hlist_add_head_rcu是遍历散列表的标准函数。
除了管理PID之外,内核还负责提供机制来生成唯一的PID。为跟踪已经分配和仍然可用的PID,内核使用一个大的位图,其中每个PID由一个比特标识。 PID的值可通过对应比特在位图中的位置计算而来。因此,分配一个空闲的PID,本质上就等同于寻找位图中第一个值为0的比特,接下来将该比特设置为1。反之,释放一个PID可通过将对应的比特从1切换为0来实现。在建立一个新进程时,进程可能在多个命名空间中是可见的。对每个这样的命名空间,都需要生成一个局部PID。这是在alloc_pid中处理的,在文件kernel/pid.c内有:
struct pid *alloc_pid(struct pid_namespace *ns, pid_t *set_tid, size_t set_tid_size){ struct pid *pid; enum pid_type type; int i, nr; struct pid_namespace *tmp; struct upid *upid; int retval = -ENOMEM; /* * set_tid_size contains the size of the set_tid array. Starting at * the most nested currently active PID namespace it tells alloc_pid() * which PID to set for a process in that most nested PID namespace * up to set_tid_size PID namespaces. It does not have to set the PID * for a process in all nested PID namespaces but set_tid_size must * never be greater than the current ns->level + 1. */ if (set_tid_size > ns->level + 1) return ERR_PTR(-EINVAL); pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL); if (!pid) return ERR_PTR(retval); tmp = ns; pid->level = ns->level; for (i = ns->level; i >= 0; i--) { int tid = 0; if (set_tid_size) { tid = set_tid[ns->level - i]; retval = -EINVAL; if (tid < 1 || tid >= pid_max) goto out_free; /* * Also fail if a PID != 1 is requested and * no PID 1 exists. */ if (tid != 1 && !tmp->child_reaper) goto out_free; retval = -EPERM; if (!checkpoint_restore_ns_capable(tmp->user_ns)) goto out_free; set_tid_size--; } idr_preload(GFP_KERNEL); spin_lock_irq(&pidmap_lock); if (tid) { nr = idr_alloc(&tmp->idr, NULL, tid, tid + 1, GFP_ATOMIC); /* * If ENOSPC is returned it means that the PID is * alreay in use. Return EEXIST in that case. */ if (nr == -ENOSPC) nr = -EEXIST; } else { int pid_min = 1; /* * init really needs pid 1, but after reaching the * maximum wrap back to RESERVED_PIDS */ if (idr_get_cursor(&tmp->idr) > RESERVED_PIDS) pid_min = RESERVED_PIDS; /* * Store a null pointer so find_pid_ns does not find * a partially initialized PID (see below). */ nr = idr_alloc_cyclic(&tmp->idr, NULL, pid_min, pid_max, GFP_ATOMIC); } spin_unlock_irq(&pidmap_lock); idr_preload_end(); if (nr < 0) { retval = (nr == -ENOSPC) ? -EAGAIN : nr; goto out_free; } pid->numbers[i].nr = nr; pid->numbers[i].ns = tmp; tmp = tmp->parent; } /* * ENOMEM is not the most obvious choice especially for the case * where the child subreaper has already exited and the pid * namespace denies the creation of any new processes. But ENOMEM * is what we have exposed to userspace for a long time and it is * documented behavior for pid namespaces. So we can't easily * change it even if there were an error code better suited. */ retval = -ENOMEM; get_pid_ns(ns); refcount_set(&pid->count, 1); spin_lock_init(&pid->lock); for (type = 0; type < PIDTYPE_MAX; ++type) INIT_HLIST_HEAD(&pid->tasks[type]); init_waitqueue_head(&pid->wait_pidfd); INIT_HLIST_HEAD(&pid->inodes); upid = pid->numbers + ns->level; spin_lock_irq(&pidmap_lock); if (!(ns->pid_allocated & PIDNS_ADDING)) goto out_unlock; for ( ; upid >= pid->numbers; --upid) { /* Make the PID visible to find_pid_ns. */ idr_replace(&upid->ns->idr, pid, upid->nr); upid->ns->pid_allocated++; } spin_unlock_irq(&pidmap_lock); return pid;out_unlock: spin_unlock_irq(&pidmap_lock); put_pid_ns(ns);out_free: spin_lock_irq(&pidmap_lock); while (++i <= ns->level) { upid = pid->numbers + i; idr_remove(&upid->ns->idr, upid->nr); } /* On failure to allocate the first pid, reset the state */ if (ns->pid_allocated == PIDNS_ADDING) idr_set_cursor(&ns->idr, 0); spin_unlock_irq(&pidmap_lock); kmem_cache_free(ns->pid_cachep, pid); return ERR_PTR(retval);}
相关推荐:《Linux视频教程》
Atas ialah kandungan terperinci Mari analisa nombor ID proses teknik klasik Linux. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!