1. malloc的实现方式
uclibc中,用户空间的malloc提供了三种实现方式:
malloc
malloc-simple
malloc-standard
具体使用何种方式,取决于.config文件定义(projectxxxconfig ormalconfig.uClibc),文件中分别对应几个配置符:
MALLOC
MALLOC_SIMPLE
MALLOC_STANDARD
malloc-standard :
This is a version (aka dlmalloc) of malloc/free/realloc written byDoug Lea,通过算法在用户空间实现内存管理
malloc-simple:
malloc/free通过简单mmap/munmap实现,代价比较大
malloc:
使用sbrk-->brk 系统调用实现,速度比mmap快
注意:malloc具体实现与其libc版本有关,例如对于glibc,最新版本只有一种实现方式:ptmalloc。
2. execve使用方法
execve为系统调用函数,功能为启动新的程序:
函数定义 int execve(const char *filename, char *const argv[ ], char *const envp[ ]);
返回值 函数执行成功时没有返回值,执行失败时的返回值为-1.
函数说明 execve()用来执行参数filename字符串所代表的文件路径,第二个参数是利用数组指针来传递给执行文件,并且需要以空指针(NULL)结束,最后一个参数则为传递给执行文件的新环境变量数组。
1 #include<stdio.h> 2 #include<stdlib.h> 3 4 int main(int arg, char **args) 5 { 6 //char *argv[]={"ls","-al","/home", NULL}; 7 char *argv[]={"free",NULL}; 8 //char *envp[]={0,NULL}; //传递给执行文件新的环境变量数组 9 10 execve("/usr/bin/free",argv,NULL); 11 //execve("/bin/ls",argv,envp); 12 }
argv:必须是数组指针,传入NULL或者字符串会出错(例如execve("/usr/bin/free","free",NULL);)
envp:可以设置为NULL
fork()和execve()的区别
fork是分身,execve是变身。
exec系列的系统调用是把当前程序替换成要执行的程序,而fork用来产生一个和当前进程一样的进程(虽然通常执行不同的代码流)。exec系列的系统调用已经是变成别的程序了,已经和本程序无关了。通常运行另一个程序,而同时保留原程序运行的方法是,fork+exec。
fork调clone, clone调do_fork(do_fork是内核函数, 不是系统调用). 当然fork也可以直接调do_fork, 具体怎么做的, 请参看内核代码sys_fork的实现.
pthread_create是调的clone.
简单的说clone就是fork的"泛化"版. 通过clone创建一个新进程时可以指定很多参数, 比如是否共享内存空间等等. Linux内核并没有对线程的实现, 一切都是进程, 线程简单地说不过是共享内存空间的进程而已(当然可能还有点别的细节, 比如是否共享信号处理, 文件描述符之类的).内核中一个task_struct对象代表一个进程/task/调度对象.
对Linux系统来说, 创建一个新线程和创建一个新进程开销是基本一样的(简单的说内核的眼里只有进程, 或者只有任务). 线程切换的开销和进程切换的开销, 主要是切换时是否刷新页表(MMU TLB), 也就是是否切换虚拟内存空间所对应的物理内存页. 别的几乎一致. 嗯, 线程切换是要快一些.
fork<app> --> __libc_fork <libc> --> pid = ARCH_FORK ()<与处理器架构相关> -->
INLINE_SYSCALL (clone, 5,
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID | SIGCHLD,
NULL, NULL, NULL, &THREAD_SELF->tid)
< (linuxuclibclibpthread ptlsysdepsunixsysvlinuxarmfork.c>
execve<app> -->
_syscall3(int, execve, const char *, filename,
char *const *, argv, char *const *, envp) <libc>
system<app> --> __libc_system <libc> -> vfork+ execl
execl --> execve
系统调用服务例程sys_clone, sys_fork, sys_vfork三者最终都是调用do_fork函数完成:
linux-3.4.xarcharmkernelsys_arm.c
1 /* Fork a new task - this creates a new program thread. 2 * This is called indirectly via a small wrapper 3 */ 4 asmlinkage int sys_fork(struct pt_regs *regs) 5 { 6 return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL); 7 8 } 9 10 /* Clone a task - this clones the calling program thread. 11 * This is called indirectly via a small wrapper 12 */ 13 asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp, 14 int __user *parent_tidptr, int tls_val, 15 int __user *child_tidptr, struct pt_regs *regs) 16 { 17 if (!newsp) 18 newsp = regs->ARM_sp; 19 20 return do_fork(clone_flags, newsp, regs, 0, parent_tidptr, child_tidptr); 21 } 22 23 asmlinkage int sys_vfork(struct pt_regs *regs) 24 { 25 return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL); 26 } 27 28 /* sys_execve() executes a new program. 29 * This is called indirectly via a small wrapper 30 */ 31 asmlinkage int sys_execve(const char __user *filenamei, 32 const char __user *const __user *argv, 33 const char __user *const __user *envp, struct pt_regs *regs) 34 { 35 int error; 36 char * filename; 37 38 filename = getname(filenamei); 39 error = PTR_ERR(filename); 40 if (IS_ERR(filename)) 41 goto out; 42 error = do_execve(filename, argv, envp, regs); 43 putname(filename); 44 out: 45 return error; 46 }
clone 和fork,vfork系统调用在实现时都是调用核心函数do_fork:
do_fork(unsigned long clone_flag, unsigned long usp, struct pt_regs);
do_fork的第一个参数是clone_flag,该参数可以是:
CLONE_VM, CLONE_FS, CLONE_FILES, CLONE_SIGHAND,CLONE_PID,CLONE_VFORK等等标志位的组合。任何一位被置1了则表明创建的子进程和父进程共享该位对应的资源。
fork时clone_flag = SIGCHLD;
vfork时clone_flag = CLONE_VM | CLONE_VFORK | SIGCHLD;
而在clone中,clone_flag由用户给出。
在vfork的实现中,cloneflags = CLONE_VFORK | CLONE_VM | SIGCHLD,这表示子进程和父进程共享地址空间,同时
do_fork会检查CLONE_VFORK,如果该位被置1了,子进程会把父进程的地址空间锁住,直到子进程退出或执行exec时才释放该锁。
pthread_create
__pthread_create_2_1 -> create_thread ->
clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL
| CLONE_SETTLS | CLONE_PARENT_SETTID
| CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
do_clone (pd, attr, clone_flags, start_thread,
STACK_VARIABLES_ARGS, 1);
-->
ARCH_CLONE (fct, STACK_VARIABLES_ARGS, clone_flags,
pd, &pd->tid, TLS_VALUE, &pd->tid) == -1)
-->
DO_CALL (clone)
3. 线程/进程
0号进程 swapper: 有一个专门的task_struct结构体类型变量init_task,以它为头系统每个线程的task_struct字段都链接在一起形成一个双向循环链表;它是系统创建的第一个进程,也是唯一一个没有通过fork|kernel_thread产生的进程,完成最开始的初始化后最终会转化为Idle进程。
1号进程 init: 进程0最终会通过调用kernel_thread创建一个内核线程去执行init函数,init函数继续完成剩余的内核初始化,并在函数的最后调用execve系统调用装入用户空间的可执行程序/sbin/init,这时进程1就拥有了自己的属性资源,成为一个普通进程,至此,内核初始化和启动过程结束。下面就进入了用户空间的初始化,init也将变为守护进程监视系统其他进程,收集孤立的进程:当一个进程启动了一个子进程并且在子进程之前终止了,这个子进程立刻成为init的子进程。由init进程领养的进程终止时init就会调用一个wait函数取得其终止状态。
2号进程kthreadd:kthreadd进程由idle通过kernel_thread创建,并始终运行在内核空间, 负责所有内核线程的调度和管理
进程 = 一段程序+堆栈空间+任务控制块+独立存储空间
线程 = 一段程序+堆栈空间+任务控制块
对内核空间来说,线程和进程没有任何区别,因为所有内核线程都使用同一块地址空间(3GB--4GB),对应同一个页表(init_mm.pgd);
对用户空间来说,进程拥有独立的页表(task_struct->mm_struct->pgd),而线程没有独立页表,与进程共享同一个mm_struct;
4. log_buf
(1)配置
内核的日志存在log_buf指向的内存缓冲区中,大小在新的内核是可配置的:
defconfig
CONFIG_LOG_BUF_SHIFT=20 (默认是17,即2的17次方)
或者:kernel/init/Kconfig:
config LOG_BUF_SHIFT
int "Kernel log buffer size (16 => 64KB, 17 => 128KB)"
range 12 21
default 17 ----------->将default值调大
help
Select kernel log buffer size as a power of 2.
Examples:
17 => 128 KB
16 => 64 KB
15 => 32 KB
14 => 16 KB
13 => 8 KB
12 => 4 KB
(2)使用
API
(1) printk:内核代码中常见的日志输出方式
(2)do_syslog/syslog: 内核提供的系统调用,用来操作log_buf
(3)klogctl:C库中提供的函数,用来使用上面的系统调用。
syslog系统调用支持下面的命令:
* 0 -- Close the log. Currently a NOP.
* 1 -- Open the log.Currently a NOP.
* 2 -- Read from the log.
* 3 -- Read all messages remaining in the ring buffer.
* 4 -- Read and clear allmessages remaining in the ring buffer
* 5 -- Clear ring buffer.
* 6 -- Disable printk toconsole
* 7 -- Enable printk toconsole
* 8 -- Set level of messagesprinted to console
* 9 -- Return number ofunread characters in the log buffer
* 10 -- Return size of the logbuffer
Tool
在shell中可以使用下面的方法直接操作内核log日志
(1) dmesg:用来查看log_buf中的数据;
(2) cat /proc/kmsg :也可用来读取log_buf中的数据
(3)data.save.b d:/log.txt __log_buf++0xffff
5. 一些调试命令
(1) cat /proc/meminfo ==>查看内存信息
(2) top
cat /proc/<pid>/status ==>查看进程状态
(3)sync
echo 3 > /poc/sys/vm/drop_caches ==> 释放系统cache占用的内存
free -m
To free pagecache, use
echo 1 > /proc/sys/vm/drop_caches
to free dentries and inodes, use
echo 2 > /proc/sys/vm/drop_caches
to free pagecache, dentries and inodes, use
echo 3 >/proc/sys/vm/drop_caches
(4)echo scan > /sys/kernel/debug/kmemleak ==> 扫描内存泄漏信息
cat /sys/kernel/debug/kmemleak
使用前需要把内核配置打开
CONFIG_HAVE_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_DEBUG_KMEMLEAK_EARLY_LOG_SIZE=400
(5) vm_stat变量是一个用于记录内存信息的大数组,数组第一项意义为NR_FREE_PAGES,即系统当前空闲页表,该值*4即为当前空闲内存。数组其他值的意义可以参阅<mmzone.h>。