copy from : http://gityuan.com/2017/08/06/linux_process_pid/
一. 概述
Android系統創建進程,最終的實現還是調用linux fork方法,對於linux系統每個進程都有唯一的 進程ID(值大於0),也有pid上限,默認為32768。 pid可重復利用,當進程被殺后會回收該pid,以供后續的進程pid分配。
上一篇文章Linux進程管理 詳細地介紹了進程fork過程,在copy_process()過程,執行完父進行文件、內存等信息的拷貝,緊接着便是執行alloc_pid()方法去分配pid.
二. 分配法
2.1 copy_process
static struct task_struct *copy_process(unsigned long clone_flags, unsigned long stack_start, unsigned long stack_size, int __user *child_tidptr, struct pid *pid, int trace, unsigned long tls) { ... struct task_struct *p; if (pid != &init_struct_pid) { //分配pid[見小節2.2] pid = alloc_pid(p->nsproxy->pid_ns_for_children); } p->pid = pid_nr(pid); //設置pid[見小節2.4] ... }
2.2 alloc_pid
[-> kernel/kernel/pid.c]
struct pid *alloc_pid(struct pid_namespace *ns)
{
struct pid *pid; //[見小節2.2.1] enum pid_type type; int i, nr; struct pid_namespace *tmp; //[見小節2.2.4] struct upid *upid; int retval = -ENOMEM; //分配pid結構體的內存 pid = kmem_cache_alloc(ns->pid_cachep, GFP_KERNEL); ... tmp = ns; pid->level = ns->level; for (i = ns->level; i >= 0; i--) { nr = alloc_pidmap(tmp); //分配pid【見小節2.3】 ... pid->numbers[i].nr = nr; //nr保存到pid結構體 pid->numbers[i].ns = tmp; tmp = tmp->parent; } ... get_pid_ns(ns); atomic_set(&pid->count, 1); for (type = 0; type < PIDTYPE_MAX; ++type) INIT_HLIST_HEAD(&pid->tasks[type]); //初始化pid的hlist結構體 upid = pid->numbers + ns->level; spin_lock_irq(&pidmap_lock); if (!(ns->nr_hashed & PIDNS_HASH_ADDING)) goto out_unlock; for ( ; upid >= pid->numbers; --upid) { //建立pid_hash的關聯關系 hlist_add_head_rcu(&upid->pid_chain, &pid_hash[pid_hashfn(upid->nr, upid->ns)]); upid->ns->nr_hashed++; } spin_unlock_irq(&pidmap_lock); return pid; ... }
2.2.1 pid結構體
[-> kernel/include/linux/pid.h]
struct pid { atomic_t count; unsigned int level; struct hlist_head tasks[PIDTYPE_MAX]; //見enum pid_type struct rcu_head rcu; struct upid numbers[1]; //見結構體upid };
2.2.2 upid結構體
[-> pid.h]
struct upid { int nr; struct pid_namespace *ns; struct hlist_node pid_chain; };
2.2.3 pid_type
[-> pid.h]
enum pid_type { PIDTYPE_PID, //進程ID PIDTYPE_PGID, //進程組ID PIDTYPE_SID, //會話組ID PIDTYPE_MAX, __PIDTYPE_TGID //僅用於__task_pid_nr_ns() };
2.2.4 pid_namespace結構體
[-> kernel/include/linux/pid_namespace.h]
struct pid_namespace { struct kref kref; struct pidmap pidmap[PIDMAP_ENTRIES]; struct rcu_head rcu; int last_pid; unsigned int nr_hashed; struct task_struct *child_reaper; struct kmem_cache *pid_cachep; unsigned int level; struct pid_namespace *parent; ... struct user_namespace *user_ns; struct work_struct proc_work; kgid_t pid_gid; int hide_pid; int reboot; struct ns_common ns; };
PID命名空間,這是為系統提供虛擬化做支撐的功能。
2.3 alloc_pidmap
[-> kernel/kernel/pid.c]
static int alloc_pidmap(struct pid_namespace *pid_ns) { //last_pid為上次分配出去的pid int i, offset, max_scan, pid, last = pid_ns->last_pid; struct pidmap *map; pid = last + 1; if (pid >= pid_max) pid = RESERVED_PIDS; //默認為300 offset = pid & BITS_PER_PAGE_MASK; //最高位值置0,其余位不變 map = &pid_ns->pidmap[pid/BITS_PER_PAGE]; //找到目標pidmap //當offset =0,則掃描一次; //當offset!=0,則掃描兩次 max_scan = DIV_ROUND_UP(pid_max, BITS_PER_PAGE) - !offset; for (i = 0; i <= max_scan; ++i) { if (unlikely(!map->page)) { void *page = kzalloc(PAGE_SIZE, GFP_KERNEL); spin_lock_irq(&pidmap_lock); if (!map->page) { map->page = page; page = NULL; } spin_unlock_irq(&pidmap_lock); kfree(page); if (unlikely(!map->page)) break; } //當pidmap還有可用pid時 if (likely(atomic_read(&map->nr_free))) { do { //當offset位空閑時返回該pid if (!test_and_set_bit(offset, map->page)) { atomic_dec(&map->nr_free); //可用pid減一 set_last_pid(pid_ns, last, pid); //設置last_pid return pid; } //否則,查詢下一個非0的offset值 offset = find_next_offset(map, offset); 根據offset轉換成相應的pid pid = mk_pid(pid_ns, map, offset); } while (offset < BITS_PER_PAGE && pid < pid_max); } //當上述pid分配失敗,則再次查找offset if (map < &pid_ns->pidmap[(pid_max-1)/BITS_PER_PAGE]) { ++map; offset = 0; } else { map = &pid_ns->pidmap[0]; offset = RESERVED_PIDS; if (unlikely(last == offset)) break; } pid = mk_pid(pid_ns, map, offset); } return -1; }
pid允許分配的最大值為32767,當pid分配輪過一圈之后則允許分配的最小值為300,也就是說前300個pid是不可再分配的。
相關常量如下:
#define PAGE_SHIFT 12 #define PAGE_SIZE (1UL << PAGE_SHIFT) // 2^12 #define BITS_PER_PAGE (PAGE_SIZE * 8) // 2^15 #define BITS_PER_PAGE_MASK (BITS_PER_PAGE-1) //2^15-1 #define PAGE_MASK (~(PAGE_SIZE-1))
2.3.1 pidmap結構體
[-> kernel/include/linux/pid_namespace.h]
struct pidmap { atomic_t nr_free; //可用pid的個數 void *page; //用於存放位圖 };
pidmap->page的大小為4KB,每一個bit位代表一個進程pid的分配情況,那么4KB*8=32768, 這正好是pid可分配的上限,用nr_free代表該namespace下還有多少可用pid。
2.3.2 find_next_offset
[-> pid.c]
#define find_next_offset(map, off) \ find_next_zero_bit((map)->page, BITS_PER_PAGE, off) static inline int mk_pid(struct pid_namespace *pid_ns, struct pidmap *map, int off) { return (map - pid_ns->pidmap)*BITS_PER_PAGE + off; }
2.4 pid_nr
[-> kernel/include/linux/pid.h]
static inline pid_t pid_nr(struct pid *pid) { pid_t nr = 0; if (pid) nr = pid->numbers[0].nr; return nr; }
根據pid結構體找到真正的pid數值。
三. 總結
- pid分配上限的查詢方式
cat /proc/sys/kernel/pid_max
,Android系統一般默認為32768。 - 對於pid<300的情況值允許分配一次,不可再改變。也就是進程pid分配范圍為(300, 32768);
- 每個pid分配成功,便會把當前的pid設置到last_pid, 那么下次pid的分配便是從last_pid+1開始 往下查找。這就意味着當last_pid+1或者附近的進程,剛被殺並回收該pid,此時再創建新進程,很有可能會復用 pid.
- 位圖法記錄已分配和未分配pid,由於pid的最大上限為32768,故pidmap采用4KB大小的內存,每一位代表一個進程ID號,正好4K*8=32K= 32768。