linux 大概架构:
linux 内核主要两部分:
机制:提供什么功能 策略:何时使用这些功能。
隔离:
详细的架构图:
第一个helloword例程: Makefile: 在Makefile 中如果是一条命令就需要用Tab 的方式,这是规矩。
CUREENT_PATH := $(shell pwd)#current path
KERNEL_VER := $(shell uname -r)# linux kernel code version
KERNELDIR ?= /lib/modules/$(KERNEL_VER)/build
all:
make -C $(KERNELDIR) M=$(CUREENT_PATH) modules
clean:
make -C $(KERNELDIR) M=$(CUREENT_PATH) clean
c文件:这里面的打印使用的是printk ,k代表的是kernel 的意思:
#include <linux/init.h>
#include <linux/module.h>
/**
* module init fuction lipk_init
* */
static int __init lpk_init(void){
printk(">>>>lpk init \n");
return 0;
}
static void __exit lpk_exit(void){
printk(">>>>lpk exit \n");
}
module_init(lpk_init);
module_exit(lpk_exit);
MODULE_LICENSE("GPL");
可以通过lnsmod 来进行加载模块 rmmod 来卸载模块 lsmod 查看是否加载成功。 通过dmesg 来查看日志。
通过下面命令来clone kernel 的代码: git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
内存寻址:
控制寄存器0-31 位置: 0 --->PE位(protedted enable) 是否进入保护模式 31 --->PG位(page enable) 是否允许分页
| PG | PE | 方式 |
|---|---|---|
| 0 | 0 | 实模式8080操作 |
| 0 | 1 | 保护模式,不允许分页 |
| 1 | 0 | 出错 |
| 1 | 1 | 允许分页的保护模式 |
编译:gcc -S helloword.c -o helloword.s 生成汇编文件
汇编:gcc -c helloword.s -o helloword.o
链接:gcc helloword.c -o a.out
装载并执行:./a.out
反汇编:objdump -d a.out
反汇编的代码: 最左边的是虚拟地址, 中间的是指令码, 右边的是AT&T格式的汇编指令
1149: f3 0f 1e fa endbr64
114d: 55 push %rbp
114e: 48 89 e5 mov %rsp,%rbp
1151: 48 8d 3d ac 0e 00 00 lea 0xeac(%rip),%rdi # 2004 <_IO_stdin_used+0x4>
1158: e8 f3 fe ff ff callq 1050 <puts@plt>
115d: b8 00 00 00 00 mov $0x0,%eax
1162: 5d pop %rbp
1163: c3 retq
1164: 66 2e 0f 1f 84 00 00 nopw %cs:0x0(%rax,%rax,1)
116b: 00 00 00
116e: 66 90 xchg %ax,%ax
通过下图可以看到,cpu 把虚拟地址送给MMU,MMU是硬件处理单元,然后通过下面分段机制转化为线性地址,然后再通过分也机制转化为物理地址,然后MMU再把这个地址送给存储单元的过程。
保护模式存在的意义:ring0 ring1 ring2 ring3 内核就是在ring0中,决定着最高特权级,ring3代表着最低特权级(应用层) 这里高特权集可以访问低特权集,反之不能。
描述符表:
全局描述符表GDT(Global descriptor table) 中断描述符表IDT(Interrupt descriptor table) 局部描述符表LDT(Local descriptor table)
Linux段:
线性地址=段的起始地址+偏移量
Linux分页机制
页的大小,在32位的操作系统中,一般默认为4K 大小,页可以2M 4M大小。 在64位的操作系统中,一般默认为4K 8K 最大可以达到256M。 分页的目的:使得每个进程都有自己独立的虚拟内存空间。 映射函数:pa=f(va) 页表存放的是一种虚拟地址和物理地址的映射关系。
0x1000 是4k
0x100000 是1M
虚拟地址->物理地址
MMU虚拟地址(逻辑地址)首先经过分段转化为线性地址,这里有两个部分:1.选择符;2,偏移量。 这里其实就是将进程中的代码和数据段分配在不同的虚拟地址段,避免进程间的互相影响。首先在段选择符中在段描述表中,找到段描述符,也就是某一个段的基地址,再加上段内偏移量,就是得到了对应的线性地址。 再次经过分页的形式将线性地址转化为物理地址。
页分为4级页表:
PGD(page Global dictionary)
PUD (page Upper dictionary)
PMD(page Middle dictionary)
PT( page table)
这四个不一定全用,在有的操作系统中,有可能将中间两个页表置为0. 仍然保留。
CR3 寄存器是保存页全局目录的地址。
进程:
程序一旦运行就形成了进程。 一般通过fork 命令来创建进程。 所有的进程的祖先无一例外时init进程。也称之为0号进程。
打印所有进程:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux>
static void printk_pid(void){
struct task_struct *task,*p;
struct list_head *pos;
int count = 0;
printk("hello world enter begin:\n");
task = &init_task;
list_for_each(pos,&task->tasks){
p = list_entry(pos,struct task_struct,tasks);
count ++;
printk("%d---->%s\n",p->pid,p->comm);
}
printk("the number of process is:%d\n",count);
}
static int __init print_pid_init(void){
printk(">>>>>print_pid_init \n");
printk_pid();
return 0;
}
static void __exit print_pid_exit(void){
printk(">>>>>print_pid_init \n");
}
module_init(print_pid_init);
module_exit(print_pid_exit);
MODULE_LICENSE("GPL");
进程时系统资源分配的基本单位,线程时独立运行的基本单位
进程/线程/内核线程 在内核中都是通过do_fork 进行创建。
内核中通过:
kthread_create() 来创建线程。最终调用到do_fork 来做事情。
do_fork 位于fork.c 中:
/*
* Ok, this is the main fork-routine.
*
* It copies the process, and if successful kick-starts
* it and waits for it to finish using the VM if required.
*/
long do_fork(unsigned long clone_flags,
unsigned long stack_start,
unsigned long stack_size,
int __user *parent_tidptr,
int __user *child_tidptr)
do_fork 所做的事情:
1.copy_process 复制父进程的进程控制块
2.pid = get_task_pid(p, PIDTYPE_PID); 获得子进程的pid
3.wake_up_new_task(p);设置子进程的状态为就绪,并且将子进程加入到就绪队列。
fork完成以后是和父进程一摸一样的进程,只有执行exec 后才是一个新的进程,创建的进程完成所有的代码,会退出,活着在main中return 也可以退出。或者被其他的杀死。也可以退出。
参考资料: www.kerneltravel.net/