上一篇blog大体上说明了一下内核中pid的管理方式,留下了1个问题和一些没有说清楚的细节:
1.多个进程共享一个pid结构
找了一遍代码,发现在fs/exec.c中有调用attach_pid调用,这个调用的条件是在一个进程fork出一个线程,同时这个线程调用了exec类函数,可以想到线程执行exec类函数,导致了整个线程组的内存结构的变化,线程在执行exec类函数时,调用了函数de_thread函数,这个函数的会杀死进程线程组中的其他的线程,包括主线程,同时把当前线程变成线程组中的主线程,同时把pid,attach到原来的主线程上。同时后面会在执行release_task,这个函数是释放进程zombie状态下剩余的内存结构。
也就是说在attach函数和release_task函数中间多个进程会共用一个pid结构。
引用一段源代码中的注释 2.6.35.13 fs/exec.c:880
880 /* Become a process group leader with the old leader's pid.
881 * The old leader becomes a thread of the this thread group .
882 * Note: The old leader also uses this pid until release_task
883 * is called. Odd but simple and correct.
884 */
下de_thread函数做的一些事情:
819 if (signal_group_exit(sig)) { //对整个group发退出信号
820 /*
821 * Another group action in progress, just
822 * return so that the signal is processed.
823 */
824 spin_unlock_irq(lock);
825 return -EAGAIN;
826 }
827
828 sig->group_exit_task = tsk; //group_exit_tas变量还没太明白搞
829 sig->notify_count = zap_other_threads(tsk); //等待线程组中除了tsk线程的退出
830 if (!thread_group_leader(tsk))
831 sig->notify_count--;
832
833 while (sig->notify_count) {
834 __set_current_state(TASK_UNINTERRUPTIBLE);
835 spin_unlock_irq(lock);
836 schedule();
837 spin_lock_irq(lock);
838 }
841 /*
842 * At this point all other threads have exited, all we have to
843 * do is to wait for the thread group leader to become inactive,
844 * and to assume its PID:
845 */
846 if (!thread_group_leader(tsk)) { //如果当前不是线程组主线程,后面会把当前pid,attach到主线程上
847 struct task_struct *leader = tsk->group_leader;
848
849 sig->notify_count = -1; /* for exit_notify() */
850 for (;;) {
851 write_lock_irq(&tasklist_lock);
852 if (likely(leader->exit_state)) //等待主线程的结束
853 break;
854 __set_current_state(TASK_UNINTERRUPTIBLE);
855 write_unlock_irq(&tasklist_lock);
856 schedule();
880 /* Become a process group leader with the old leader's pid.
881 * The old leader becomes a thread of the this thread group.
882 * Note: The old leader also uses this pid until release_task
883 * is called. Odd but simple and correct.
884 */
885 detach_pid(tsk, PIDTYPE_PID);
886 tsk->pid = leader->pid; //获得主线程的pid结构
887 attach_pid(tsk, PIDTYPE_PID, task_pid(leader)); //把当前线程的pid attach到主线程的pid上,这时pid的tasks会有多个线程结构(task_struct)
888 transfer_pid(leader, tsk, PIDTYPE_PGID);
889 transfer_pid(leader, tsk, PIDTYPE_SID);
890
891 list_replace_rcu(&leader->tasks, &tsk->tasks);
892 list_replace_init(&leader->sibling, &tsk->sibling);
893
894 tsk->group_leader = tsk;
895 leader->group_leader = tsk;
896
897 tsk->exit_signal = SIGCHLD;
898
899 BUG_ON(leader->exit_state != EXIT_ZOMBIE);
900 leader->exit_state = EXIT_DEAD;
901 write_unlock_irq(&tasklist_lock);
902
903 release_task(leader); //这时释放掉主线程的内存结构。
说明一下:每个task_struct的thread_group字段是内核中hlist中的一个节点,也就是说通过这个字段,通过container_of函数来映射出task_struct结构体,在fork函数中会初始化这个thread_group,如果是fork的线程,那个会把这个task_struct,放到父进程(主线程)的thread_group中,也就是说每个线程的task_group中所代表的线程组中都有当前进程的task_struct,每个线程的主线程就是这些线程的父进程。
代码:Kernel/fork.c
1258 if (clone_flags & CLONE_THREAD) { //线程
1259 current->signal->nr_threads++;
1260 atomic_inc(¤t->signal->live);
1261 atomic_inc(¤t->signal->sigcnt);
1262 p->group_leader = current->group_leader;
1263 list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group); //把当前的线程加入到父进程的线程组中
1264 }
2.寻找空闲的pid
没有说清楚的就是如何从一个bitmap中寻找位为0的位置。上篇的分析我们知道,pid的分配的情况被记在了pid_namespace中的pid_map中,pid_map被看做是一个bitmap,被用过的位置置为1,没有用过的位置位为0,寻找位为0的函数是find_next_zero_bit。这个函数的思想就是把bitmap切成多个long(连续64位)来看,然后根据位移屏蔽到一些无关的为1的位(offset之前的位不看),然后取反,可知,如果这个取反之前如果有一位为0,那么取反之后的long的值肯定会大于0,那么如果剩下的long的值大于0,就可以判断在64位中有为0的位,那么在用汇编bsf指令找出这个为0的位置。所以函数分为两个步骤,第一步是确定一个范围内有没有0的位,第二步就是如果有0的位置,把它从中找出来。
find_next_bit.c:
67 unsigned long find_next_zero_bit(const unsigned long *addr, unsigned long size,
68 unsigned long offset) //addr是pid_map的首地址,size是这个pid_map的规模,offset是从哪个位移开始寻找位为0的位置。
69 {
70 const unsigned long *p = addr + BITOP_WORD(offset);
71 unsigned long result = offset & ~(BITS_PER_LONG-1);
72 unsigned long tmp;
73
74 if (offset >= size)
75 return size;
76 size -= result; //2
77 offset %= BITS_PER_LONG;
78 if (offset) {
79 tmp = *(p++);
80 tmp |= ~0UL >> (BITS_PER_LONG - offset);
81 if (size < BITS_PER_LONG) //如果size不足1个long型变量,
82 goto found_first;
83 if (~tmp) //tmp取反如果大于0,代表在这段空间中有0位
84 goto found_middle;
85 size -= BITS_PER_LONG;
86 result += BITS_PER_LONG;
87 }
88 while (size & ~(BITS_PER_LONG-1)) { //遍历下一个64位空间
89 if (~(tmp = *(p++)))
90 goto found_middle;
91 result += BITS_PER_LONG;
92 size -= BITS_PER_LONG;
93 }
94 if (!size)
95 return result;
96 tmp = *p;
97
98 found_first:
99 tmp |= ~0UL << size;
100 if (tmp == ~0UL) /* Are any bits zero? */
101 return result + size; /* Nope. */
102 found_middle:
103 return result + ffz(tmp);
104 }
举例说明整个函数的思想:
根据上图中假设上面有250位的内存地址空间,那么首地址就是addr,size是250,offset70,那么这个这个函数的目的就是寻找addr开始,最大位移为250的地址空间,从位移是70的位置开始寻找后面是否位是0的位置。
那么整个地址空间被切割成很多个64位来处理,因为可以把这64位转化为1个long型的变量,所以第一步就是取得包括位移为70的这个long型变量的首地址。如果这个offset这个位移求64位的掩码大于0,证明这个offset是在这个64位的中间位置(不是第一位),那么就到了tmp |= ~0UL >> (BITS_PER_LONG –offset);这里tmp就是long变量的值,~0UL操作就是64位1,然后向右移动64-6=58位,那么~0UL>> (BITS_PER_LONG –offset)结果就是高位58个0,和低位的6个1,那么这个tmp再和前面那个结果做与的操作,那么可以知道tmp的6个低位肯定都是1,tmp中后面58位该是什么还是什么。这时再对tmp取反操作,那么低位都变成0了,高位0变为1,1变为0,这个如果tmp大于0的话就代表高位58位有为0的位,那么对应到寻找pid_map中为0的位,那么就可以确定有0的位置了。
确认tmp中有1的位,那么就该寻找这个位究竟在什么位置了,通过fzz函数
361 static inline unsigned long ffz(unsigned long word)
362 {
363 asm("bsf %1,%0"
364 : "=r" (word)
365 : "r" (~word));
366 return word;
367 }
从网上摘了一段关于bsf指令的说明:
bsfl汇编指令:
Intel汇编指令:bsf
oprd1,oprd2;
顺向位扫描(bitscan forward)
从右向左(从位0-->位15或位31)扫描字或双字操作数oprd2中第一个含"1"的位,并把扫描到的第一个含'1'的位的位号送操作数oprd1 。
参考文档:
1.http://blog.csdn.net/dog250/article/details/5303654