gcc -c -fPIC process.c
gcc -c -fPIC createprocess.c //-fPIC不可丢
ar cr libstaticprocess.a process.o//打包为一个静态链接库
gcc -o staticprocess createprocess.o -L. -lstaticprocess//使用
gcc -shared -fPIC -o libdynamicprocess.so process.o //创建一个动态库
gcc -o dynamicprocess createprocess.o -L. -ldynamicprocess//使用
go@:~$ ls -l /sbin/init
lrwxrwxrwx 1 root root 20 Sep 5 11:59 /sbin/init -> /lib/systemd/systemd
/*创建一个key值,并指定了析构函数destructor用于释放资源*/
int pthread_key_create(pthread_key_t *key,void(* destructor)(void*));
/*线程可以通过pthread_setspecific函数设置key对应的value*/
int pthread_setspecific(pthread_key_t key,const void * value);
/*使用pthread_getspecific函数获取key对应的值*/
void *pthread_getspecific(pthread_key_t key);
待线程退出时会调用设置的析构函数释放value
struct task_struct {
。。。//linux-4.13.16\include\linux\sched.h
struct list_head tasks;//内核任务管理队列指针
pid_t pid;//当前线程id
pid_t tgid;//进程主线程id
...
struct task_struct *group_leader;//指向主线程
如上所示,基于tgid就可以知道一个task_struct代表什么结构;
/* -1 unrunnable, 0 runnable, >0 stopped: */
volatile long state;//进程运行状态
int exit_state;
/* Used in tsk->state: */
#define TASK_RUNNING 0 //常说的就绪态
#define TASK_INTERRUPTIBLE 1 //可中断的进程睡眠,如wait时
#define TASK_UNINTERRUPTIBLE 2 //不可中断只能等待此状态终结,致命信号kill也会被忽略
#define __TASK_STOPPED 4
#define __TASK_TRACED 8
/* Used in tsk->exit_state: */
#define EXIT_DEAD 16 //死亡的进程
#define EXIT_ZOMBIE 32 //刚死的进程设计的,此时为僵尸进程
。。。
#define TASK_KILLABLE (TASK_WAKEKILL | TASK_UNINTERRUPTIBLE)
/*TASK_UNINTERRUPTIBLE状态的弱化版,可被致命信号唤醒*/
上面大致是一些关于进程的状态码,对于理解清楚进程的状态终于有了进一步的认识;下面继续看及其重要的两个成员变量,他们是实现将用户态和内核态执行串起来的关键:
struct thread_info thread_info;
void *stack;
如上所示,下面贴一个简单的代码,咱们从汇编层来验证64bit下的调用正确性:
int invoke(int a){
int tmp = a+1;
return tmp;
}
void main(){
int res=invoke(1);
}
汇编如下:
invoke:
pushq %rbp
movq %rsp, %rbp
movl %edi, -20(%rbp)
movl -20(%rbp), %eax
addl $1, %eax
movl %eax, -4(%rbp)
movl -4(%rbp), %eax
popq %rbp
ret
main:
pushq %rbp //存放栈基地址
movq %rsp, %rbp//更新rbp
subq $16, %rsp //后退16字节。。。
movl $1, %edi //传递参数使用edi
call invoke //。。。
movl %eax, -4(%rbp)
nop
leave
ret
由上图可知具体内核栈布局,所以说系统调用时的保护上下文就是将相关寄存器变量保存在上图中的pg_regs结构体中,这样当返回时可以恢复现场,接着先前的指令执行下去。
总之,这是一个复杂的过程。
void *stack;
轻松找到内核栈的位置thread_info;
来定位 unsigned int policy;//调度策略
int prio; //下面几个是配合调度策略使用的优先级变量
int static_prio;
int normal_prio;
unsigned int rt_priority;
/*对于实时进程优先级范围是0-99,对于普通进程优先级范围是100-139*/
下面是几种实时进程调度策略:
#define SCHED_FIFO 1 //先来先服务
#defien SCHED_RR 2 //轮流调度算法
#define SCHED_DEADLINE 6 //选择距离deadline最近的进程调度
下面是几种普通进程调度策略:
#define SCHED_NORMAL 0 //普通进程
#define SCHED_BATCH 3 //后台进程,优先级稍低
#define SCHED_IDLE 5 //空闲进程,优先级最低
好了,以上就是这个进程运行初期就应该确认其中的某一项,然后CPU调度时就会按照某种具体的方法来调度,它主要是通过指定特定的调度类来实现的,linux上就是下面的这个task_struct 成员变量。
//linux-4.13.16\include\linux\sched.h...562 line
const struct sched_class *sched_class;
看这个变量名就知道他必定会在初始化task_struct初期就会指定具体的调度类,比若说对于优先级最高的进程就会是stop_sched_class
,对于普通进程则是fair_sched_class
,而rt_sched_class
就对应 RR 算法或者 FIFO 算法的调度策略。
对于task_struct它有进程相关的统计量,而CFS就是基于进程的vruntime来设计的,对于运行中的进程当CPU触发了一个时钟中断时,Tick一下的时候就会增加vruntime的值,在当前进程时间片结束之后,CPU会结合具体优先级根据算法更新vruntime的值,接下来找到vruntime最小的进程调度。
如上图所示,每一个CPU都有两个维护调度进程的队列,这个队列是基于红黑树实现的,红黑树上的节点就是一个个的调度实体,每当要调度一个进程时只需要在该红黑树上找到最左边的调度实体就可以了。当然在查找时首先查找实时调度队列RT,若其中没有才去CFS调度队列调度普通队列。