comments 20080708
内核通常由:
1.负责响应中断的中断服务程序
2.管理多个进程分享处理器时间的调度程序
3.负责管理进程地址空间的内存管理程序
4.网络、进程间通信的系统服务程序组成
处理器在任何指定时间点上的活动范围:1.内核空间,处于进程上下文, 代表某个特定进程在执行2.内核空间,处于中断上下文, 与任何进程无关, 处理某个特定中断3.用户空进,执行用户进程
Linux内核并不区分线程和其他一般的进程.对内核来说, 所有的进程都一样,只不过是其中的一些共享资源而已。
comments 20080709
内核开发中必须牢记的:
1.no libc
2.Gnuc 扩展. 内核使用到的c扩展
a.
inline 消除函数调用和返回的开销(寄存器存储和恢复) You should use static while defining
inline function. such as static inline void dog(int size);
内联函数必须在使用之前就定义好, 否则编译器没法把这个函数展开. 实践中一般在头文件中定义内联函数。
b. 内联汇编 在偏近底层或对执行时间有严格要求的地方,一般使用汇编语言。
c.
分支声明,对于条件选择语句,gcc内建了一条指令用于优化, 在一个条件经常出现或者该条件很少出现的时候
编译器可以根据这条指令对条件选择分支进行优化。比如 likely() unlikely() 例子: if(likely(foo)){ ...;
} /* foo 通常都不会为0 */ if(unlikely(foo)){ ...; } /* foo 通常都为0 */ 使用分支声明
一定要确定 这个条件绝大多数条件下都成立。使用得当,性能会得到提升,否则反而会下降。 unlikely()
在内核使用的更广泛,因为if往往判断一种特殊的情况。
3. 没有内存保护机制,内核中发生的内存错误会导致oops。 内核中内存都不分页,每用掉一个字节,物理内存就减少一个字节。加新功能必须注意。
4. 不要轻易在内核中使用浮点数
5. 容量小而固定的栈 内核栈大小随体系结构而变。x86上栈的大小在编译时配置可以是8k也可以是4k。 从历史上来说, 栈的大小是两页。It means, 32bit 8k 64bit 16k
6. 同步和并发 a.linux是抢占式的多任务操作系统,内核必须对多任务进行同步。 b.linux支持多处理器 c.中断是异步到来的 d.linux内核可以抢占 常有解决竞争的办法是使用自旋锁和信号量
7. 可移植性的重要性 大部分c代码应该与体系无关。诸如保持字节序,64位对齐,不假定字长和页面长度等准则都有助于移植性
Chapter 3
linux系统的线程实现非常特别, 它对线程和进程并不特别区分。对linux而言线程只不过是一种特殊的进程线程之间(同一进程中的线程)可以共享虚拟内存,但拥有各自的虚拟处理器
comments 20080710
2.6
内核为每个进程分配一个内核栈 大小为8k(两个页,可配置)。在栈的尾端保存一个thread_info结构.
这种方式便于计算thread_info的地址. current()
thread_info->task存放的是指向该任务实际task_struct的指针
x86系统上
current通过current_thread_info()计算thread_info地址。该操作把栈指针的后13位屏蔽 %esp andl
~(8192-1) (假定栈大小是8k)esp 是栈寄存器最后current
通过current_thread_info->task;得到task_struct的地址
process state:
As
its name implies, the state field of the process descriptor describes
what is currently happening to the process. It consists of an array of
flags, each of which describes a possible process state. In the current
Linux version, these states are mutually exclusive, and hence exactly
one flag of state always is set; the remaining flags are cleared. The
following are the possible process states:
TASK_RUNNING
The process is either executing on a CPU or waiting to be executed.
TASK_INTERRUPTIBLE
The
process is suspended (sleeping) until some condition becomes true.
Raising a hardware interrupt, releasing a system resource the process
is waiting for, or delivering a signal are examples of conditions that
might wake up the process (put its state back to TASK_RUNNING).
TASK_UNINTERRUPTIBLE
Like
TASK_INTERRUPTIBLE, except that delivering a signal to the sleeping
process leaves its state unchanged. This process state is seldom used.
It is valuable, however, under certain specific conditions in which a
process must wait until a given event occurs without being interrupted.
For instance, this state may be used when a process opens a device file
and the corresponding device driver starts probing for a corresponding
hardware device. The device driver must not be interrupted until the
probing is complete, or the hardware device could be left in an
unpredictable state.
TASK_STOPPED
Process execution has been
stopped; the process enters this state after receiving a SIGSTOP,
SIGTSTP, SIGTTIN, or SIGTTOU signal.
TASK_TRACED
Process
execution has been stopped by a debugger. When a process is being
monitored by another (such as when a debugger executes a ptrace( )
system call to monitor a test program), each signal may put the process
in the TASK_TRACED state.
-----------------------------------------------------
Two
additional states of the process can be stored both in the state field
and in the exit_state field of the process descriptor; as the field
name suggests, a process reaches one of these two states only when its
execution is terminated:EXIT_ZOMBIE Process execution is terminated,
but the parent process has not yet issued a wait4( ) or waitpid( )
system call to return information about the dead process. [*]Before the
wait( )-like call is issued, the kernel cannot discard the data
contained in the dead process descriptor because the parent might need
it. [*] There are other wait( ) -like library functions, such as wait3(
) and wait( ), but in Linux they are implemented by means of the wait4(
) and waitpid( ) system calls.
EXIT_DEAD
The final state: the
process is being removed by the system because the parent process has
just issued a wait4( ) or waitpid( ) system call for it. Changing its
state from EXIT_ZOMBIE to EXIT_DEAD
avoids race conditions due to other threads of execution that execute wait( )-like calls on the same process.
The value of the state field is usually set with a simple assignment.
For instance: p->state = TASK_RUNNING;
The
kernel also uses the set_task_state and set_current_state macros: they
set the state of a specified process and of the process currently
executed, respectively. Moreover, these macros ensure that the
assignment operation is not mixed with other instructions by the
compiler or the CPU control unit. Mixing the instruction order may
sometimes lead to catastrophic results.
当一个程序执行了系统调用或者触发了某个异常,它就陷入内核空间。此时,我们称内核“代表进程执行"并处于进程上下文中。系统调用和异常处理程序是对内核明确定义的接口,进程只有通过这些接口才能陷入内核执行。
系
统中所有进程都是init进程的后代,系统中的每一个进程都有一个父进程init进程的task_struct是作为init_task静态分配的。
next_task() prev_task() 实现访问前一个和后一个进程task_struct for_each_task()
遍历整个任务队列
根据元素地址得到包含它的结构地址的方法
typedef struct test{
char a[10];
int b;
short c;
}TEST;
/* ptr: pointer of the member
type: type of the container
member: member name*/
#define container_of(ptr, type, member) ({\ (type *)((char *)ptr - offsetof(type, member));})
#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
int main()
{
TEST tmp;
TEST *p;
p = container_of(&tmp.b, TEST, b);
printf("%p, %p\n", &tmp, p);
}
linux创建进程方式: fork() & exec()fork(): 通过拷贝当前进程创建一个子进程。 与父进程的区别仅仅在于pid ppid以及某些资源和统计量exec(): 负责读取可执行文件, 并将其载入地址空间开始运行
写
时拷贝,内核并不复制整个进程地址空间,而是让父子进程共享同一个拷贝,只有在写入数据的时候,数据才被复制,从而使父子进程拥有各自的拷贝。比如:
fork()后立即exec()就不需要复制整个父进程的地址空间,这时fork()的实际开销就是复制父进程的也表以及给子进程创建唯一的进程描述符。
这样可以避免拷贝大量根本不会被使用的数据。
do_fork()
在copy_process()返回后,新创建的子进程被唤醒并让其投入运行。内核有意这么做,避免写时拷贝的额外开销.vfork()不拷贝父进程的页
表项。子进程作为父进程的一个单独的线程在它的地址空间里运行, 父进程被阻塞,直到子进程退出或者执行exec()。
线程在linux里的实现从内核的角度来说,linux并没有线程的概念,他把所有的线程都当作进程来实现。线程被当作去其他进程共享某些资源的进程。每个线程都有唯一属于自己的task_struct.
内核线程 独立运行在内核空间的标准进程内核线程没有独立的地址空间 他的mm指针为NULL 只在内核空间运行。
int kernel_thread(int (*fn) (void *), void *arg, unsigned long flag)
进程退出 主要靠do_exit()完成。
1.将task_struct->flags |= PF_EXITING
2.exit_mm()放弃进程占用的mm_sturct,如果没有别的进程使用他 就彻底释放他
3.exit_sem() _exit_files() _exit_fs() exit_namespace()
4.exit_notify() 修改子进程的父进程。把当前进程状态设置为EXIT_ZOMBIE
5.schedule() 切换到其他进程运行
此时进程处于EXIT_ZOMBIE, 他所占的资源只有内核栈 task_struct thread_info.此时进程存在的唯一目的就是向父进程提供信息。
父进程执行wait4()
do_wait()->wait_task_zombie()->release_task()->_unhash_process() 从pidhash上删除该进程,同时从task_list删除该进程
最后put_task_struct释放内核栈,task_struct and thread_info。
内核通过exit_parent() 来保证结束进程的children不会成为孤儿。 在该进程所在进程组找一个进程作为其children的父进程,如果没有就以init进程作为父进程。
这样就可以避免系统中出现永远处于僵死状态的进程。
阅读(2335) | 评论(0) | 转发(0) |