道生一,一生二,二生三,三生万物。
linux进程
每个进程对应一个 task_struct
数据结构,这个数据结构包含了进程的所有的信息,这个结构非常复杂,参考linux内核结构体说明_晓镁的博客-CSDN博客。
linux操作系统不会区分线程和进程,线程也是通过进程来实现的。进程是资源管理的最小单元,线程是程序执行的最小单元。
进程有独立的资源,进程fork出来的子进程继承父进程资源。fork()后父子进程拥有相同内容的代码段、数据段和用户堆栈,实际上是父进程克隆复制了自己的PCB块。
子进程从父进程继承的资源有:用户/组号、进程组号、env,堆栈、共享内存、当前/根目录、打开的文件描述符、信号等。
子进程独有进程号、文件描述符、不继承异步输入和输出、不继承进程正文数据和memory locks。
这里感兴趣的话可以继续研究一下,fork()和vfork()父子进程执行顺序问题,及其他区别。
0 号进程是无中生有出来的,是所有进程的祖先,是为道。
1号init进程是内核启动的用户级进程,引导进程,init是第一个进程,是为一。
2号kthreadd进程是内核线程的父进程or祖先进程,是为二。
在编写内核驱动代码的时候,会经常用到kthread_create函数,其核心是使用kthread_create_list全局链维护kthread。常见的pthread_create、pthread_join、pthread_exit为用户态线程函数。
所以进程都是通过其他进程创建出来的,整进程组织架构看起来是一个树。
进程调度
yield系统调用
yield、pause让出cpu。
运行在用户态的进程,想实现一些更底层的操作,可以通过syscall进入到内核态。
yield系统调用就会使当前进程让出cpu,进行syscall系统调用。
// kernel/sched/core.c
SYSCALL_DEFINE0(sched_yield) {
do_sched_yield();
return 0;
}
系统调用通过SYSCALL_DEFINE0定义,x表示参数个数。
// kernel/sched/core.c
static void do_sched_yield(void) {
...
schedule();
...
}
schedule()函数是内核中进程调度的入口。
下面我们来看看 pause
这个系统调用:
// kernel/signal.c
SYSCALL_DEFINE0(pause) {
__set_current_state(TASK_INTERRUPTIBLE);
schedule();
}
// include/linux/sched.h
#define __set_current_state(state_value) \
current->state = (state_value)
pause系统调用
先把进程设置为TASK_INTERRUPTIBLE状态,ask_struct结构体对应的state。
标记为TASK_INTERRUPTIBLE后,后续进程调度中就会过忽略这个进程,选择其他进程进行调度。同样schedule()函数是内核中进程调度的入口。