首页 > 技术文章 > Linux的一些细节记录

DF11G 2018-12-13 19:08 原文

 

1. malloc的实现方式

uclibc中,用户空间的malloc提供了三种实现方式:

malloc

malloc-simple

malloc-standard 

具体使用何种方式,取决于.config文件定义(project\xxx\config\normal\config.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)

< (\linux\uclibc\libpthread\nptl\sysdeps\unix\sysv\linux\arm\fork.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.x\arch\arm\kernel\sys_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>。

推荐阅读