kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp
sar -n DEV 1

开机自动挂载硬盘

一、Linux磁盘分区UUID的获取

1
2
1、$ ls -l /dev/disk/by-uuid/
2、$ blkid /dev/sdb1

二、开机自动挂载

1
2
vim /etc/fstab
UUID=XXXX /XXXX ext4 defaults 0 0

我们在linux中常常用mount命令把硬盘分区或者光盘挂载到文件系统中。/etc/fstab就是在开机引导的时候自动挂载到linux的文件系统。 在linux中/etc/fstab的数据项如下所示:

1
2
/dev/device   mountpoint   type   rules   dump   order
设备名称        挂载点          分区类型   挂载选项     dump选项    fsck选项

例如这是一个普通的/etc/fstab:

1
2
3
4
5
/dev/hda2     /                    ext3        defaults   0 1
/dev/hda3     swap             swap      defaults   0 0
/dev/hda5     /usr               ext3        defaults   0 0
/dev/fdo        /mnt/flopy     ext3        noauto     0 0
/dev/cdrom    /mnt/cdrom   iso9660  noauto,ro 0 0

(1)设备名称

/dev/device就是需要挂载的设备,/hda2就是第一个IDE插槽上的主硬盘的第二个分区。如果是第二个IDE插槽主硬盘的第三个分区,那就是/dev/hdc3,具体可以在linux下使用fdisk -l 查看。

(2)挂载点

mountpoint 就是挂载点。/、 /usr、 swap 都是系统安装时分区的默认挂载点。
如果你要挂载一个新设备,你就要好好想想了,因为这个新设备将作为文件系统永久的一部分,需要根据FSSTND(文件系统标准),以及它的作用,用户需求来决定。比如你想把它做为一个共享资源,放在/home下面就是一个不错选择。

(3)分区类型

type 是指文件系统类型,下面列举几个常用的:

1
2
3
4
5
6
7
8
9
10
Linux file systems: ext2, ext3, jfs, reiserfs, reiser4, xfs, swap.
Windows:
vfat = FAT 32, FAT 16
ntfs= NTFS
Note: For NTFS rw ntfs-3g
CD/DVD/iso: iso9660
Network file systems:
nfs: server:/shared_directory /mnt/nfs nfs <options> 0 0
smb: //win_box/shared_folder /mnt/samba smbfs rw,credentials=/home/user_name/winbox-credentials.txt 0 0
auto: The file system type (ext3, iso9660, etc) it detected automatically. Usually works. Used for removable devices (CD/DVD, Floppy drives, or USB/Flash drives) as the file system may vary on thesedevices.

(4)挂载选项

rules 是指挂载时的规则。下面列举几个常用的:

1
2
3
4
5
6
7
auto 开机自动挂载
default 按照大多数永久文件系统的缺省值设置挂载定义
noauto 开机不自动挂载
nouser 只有超级用户可以挂载
ro 按只读权限挂载
rw 按可读可写权限挂载
user 任何用户都可以挂载

请注意光驱和软驱只有在装有介质时才可以进行挂载,因此它是noauto

(5)dump选项

这一项为0,就表示从不备份。如果上次用dump备份,将显示备份至今的天数。

(6)fsck选项

order 指fsck(启动时fsck检查的顺序)。为0就表示不检查,(/)分区永远都是1,其它的分区只能从2开始,当数字相同就同时检查(但不能有两1)。
如果我要把第二个IDE插槽主硬盘上的windows C 区挂到文件系统中,那么数据项是:

1
2
/dev/hdc1 /c vfat defaults 0 0
(/c 是事先建立的文件夹,作为c盘的挂载点。)

当你修改了/etc/fstab后,一定要重新引导系统才会有效。
fstab中存放了与分区有关的重要信息,其中每一行为一个分区记录,每一行又可分为六个部份,下面以/dev/hda7 / ext2 defaults 1 1为例逐个说明:

  1. 第一项是您想要mount的储存装置的实体位置,如hdb或如上例的/dev/hda7。
  2. 第二项就是您想要将其加入至哪个目录位置,如/home或如上例的/,这其实就是在安装时提示的挂入点。
  3. 第三项就是所谓的local filesystem,其包含了以下格式:如ext、ext2、msdos、iso9660、nfs、swap等,或如上例的ext2,可以参见/prco/filesystems说明。
  4. 第四项就是您mount时,所要设定的状态,如ro(只读)或如上例的defaults(包括了其它参数如rw、suid、exec、auto、nouser、async),可以参见「mount nfs」。
  5. 第五项是提供DUMP功能,在系统DUMP时是否需要BACKUP的标志位,其内定值是0。
  6. 第六项是设定此filesystem是否要在开机时做check的动作,除了root的filesystem其必要的check为1之外,其它皆可视需要设定,内定值是0。

周期性调度器scheduler_tick

周期性调度器由中断实现,系统定时产生一个中断,然后启动周期性调度器,周期性调度器执行过程中要关闭中断, 周期性调度器执行完毕后再打开中断(handle_IRQ_event, IRQF_DISABLED)

周期性调度器主要做两个工作:
a)更新相关统计量
b) 检查进程执行的时间是否超过了它对应的ideal_runtime,如果超过了,则告诉系统,需要启动主调度器(schedule)进行进程切换。(注意 thread_info:preempt_count、thread_info:flags (TIF_NEED_RESCHED))

周期性调度器

1
2
3
4
5
6
7
8
9
10
11
12
13
|---->do_timer()   更新jiffies_64
|---->update_process_times()
	|---->scheduler_tick()
	|---->update_rq_clock()  更新当前调度队列rq的clock
	|---->curr->sched_class->task_tick() 
	|         对于普通进程,即task_tick_fair()
	|         task_struct: struct sched_class *sched_class

update_rq_clock()----delta = sched_clock_cpu(cpu_of(rq)) - rq->clock
	|-----两次相邻两次周期性调度器运行的时间差
	|----rq->clock += delta; 更新运行队列上的时钟
		|---->update_rq_clock_task(rq, delta)
		|     即rq->clock_task += delta

普通进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
task_tick_fair()---->entity_tick()   没有考虑组调度
  |---->update_curr() 更新相关统计量
  |---->check_preempt_tick()   
  |        检查进程本次获得CPU使用权的执行时间是否超过了
  |        它对应的ideal_runtime值,如果超过了,则将当前进
  |        程的TIF_NEED_RESCHED标志位置位

update_curr()
  |----delta_exec = (unsigned long)(now - curr->exec_start);  
  |            exec_start当前进程开始获得
  |            cpu使用权时的时间戳;
  |            进程本次所获得的CPU执行权的时间;
  |---->__update_curr(cfs_rq, curr, delta_exec);
      |---->curr->sum_exec_runtime += delta_exec; 
      |     更新该进程获得CPU执行权总时间
      |
      |---->curr->vruntime += delta_exec_weighted;
      |     更新该进程获得CPU执行权的虚拟时间
      |
      |---->update_min_vruntime()
      |     更新cfs_rq->min_vruntime
      |
  |---->curr->exec_start = now    
  |        更新进程下次运行起始时间
  |        (如果被抢占,下次被调度时将会更新)

check_preempt_tick()
  |----ideal_runtime = sched_slice(cfs_rq, curr);
  |----delta_exec = curr->sum_exec_runtime 
  |                 - curr->prev_sum_exec_runtime;
  |----if(delta_exec > ideal_runtime)  
  |          resched_task(rq_of(cfs_rq)->curr);
  |          把当前进程的TIF_NEED_RESCHED标志位置位
  |----else
  |    delta = curr->vruntime - se->vruntime;  //这是什么?
  |    if (delta > ideal_runtime)  
  |        resched_task(rq_of(cfs_rq)->curr);
  |        把当前进程的TIF_NEED_RESCHED标志位置位

实时进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
task_tick_rt()
  |---->update_curr_rt();
  |---->if (p->policy != SCHED_RR) return;  SCHED_FIFO只有主动放弃CPU使用权
  |---->rt.timeslice值减一,若没有运行完时间则直接返回,
  |     否则再次分配时间片,加入队列尾部,设置TIF_NEED_RESCHED

update_curr_rt()
  |----delta_exec = rq->clock - curr->se.exec_start; //本次运行时间
  |----curr->se.sum_exec_runtime += delta_exec; //更新总得运行时间
  |----curr->se.exec_start = rq->clock; //更新下次进程运行的起始时间
  |----if (sched_rt_runtime(rt_rq) != RUNTIME_INF)
  |-------{
  |           rt_rq->rt_time += delta_exec;
  |                if (sched_rt_runtime_exceeded(rt_rq))
  |                   resched_task(curr);
  |       }

Linux的进程内核栈

http://blog.chinaunix.net/uid-20543672-id-2996319.html

  • 内核具有非常小的栈,它可能只和一个4096或8192字节大小的页那样小

什么是进程的“内核栈”?

在每一个进程的生命周期中,必然会通过到系统调用陷入内核。在执行系统调用陷入内核之后,这些内核代码所使用的栈并不是原先用户空间中的栈,而是一个内核空间的栈,这个称作进程的“内核栈”。

比如,有一个简单的字符驱动实现了open方法。在这个驱动挂载后,应用程序对那个驱动所对应的设备节点执行open操作,这个应用程序的open其实 就通过glib库调用了Linux的open系统调用,执行系统调用陷入内核后,处理器转换为了特权模式(具体的转换机制因构架而异,对于ARM来说普通 模式和用户模式的的栈针(SP)是不同的寄存器),此时使用的栈指针就是内核栈指针,他指向内核为每个进程分配的内核栈空间。

内核栈的作用

我个人的理解是:在陷入内核后,系统调用中也是存在函数调用和自动变量,这些都需要栈支持。用户空间的栈显然不安全,需要内核栈的支持。此外,内核栈同时用于保存一些系统调用前的应用层信息(如用户空间栈指针、系统调用参数)。

内核栈与进程结构体的关联

每个进程在创建的时候都会得到一个内核栈空间,内核栈和进程的对应关系是通过2个结构体中的指针成员来完成的:

(1)struct task_struct

在学习Linux进程管理肯定要学的结构体,在内核中代表了一个进程,其中记录的进程的所有状态信息,定义在Sched.h (include\linux)。
其中有一个成员:void *stack;就是指向下面的内核栈结构体的“栈底”。
在系统运行的时候,宏current获得的就是当前进程的struct task_struct结构体。

(2)内核栈结构体union thread_union
1
2
3
4
union thread_union {
	struct thread_info thread_info;
	unsigned long stack[THREAD_SIZE/sizeof(long)];
};

其中struct thread_info是记录部分进程信息的结构体,其中包括了进程上下文信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*
 * low level task data that entry.S needs immediate access to.
 * __switch_to() assumes cpu_context follows immediately after cpu_domain.
 */
struct thread_info {
	unsigned long    flags;    /* low level flags */
	int      preempt_count;  /* 0 => preemptable, <0 => bug */
	mm_segment_t    addr_limit;  /* address limit */
	struct task_struct  *task;    /* main task structure */
	struct exec_domain  *exec_domain;  /* execution domain */
	__u32      cpu;    /* cpu */
	__u32      cpu_domain;  /* cpu domain */
	struct cpu_context_save  cpu_context;  /* cpu context */
	__u32      syscall;  /* syscall number */
	__u8      used_cp[16];  /* thread used copro */
	unsigned long    tp_value;
	struct crunch_state  crunchstate;
	union fp_state    fpstate __attribute__((aligned(8)));
	union vfp_state    vfpstate;
#ifdef CONFIG_ARM_THUMBEE
	unsigned long    thumbee_state;  /* ThumbEE Handler Base register */
	#endif
	struct restart_block  restart_block;
};

关键是其中的task成员,指向的是所创建的进程的struct task_struct结构体

而其中的stack成员就是内核栈。从这里可以看出内核栈空间和 thread_info是共用一块空间的。如果内核栈溢出, thread_info就会被摧毁,系统崩溃了~~~

内核栈—struct thread_info—-struct task_struct三者的关系入下图:

内核栈的产生

在进程被创建的时候,fork族的系统调用中会分别为内核栈和struct task_struct分配空间,调用过程是: fork族的系统调用—>do_fork—>copy_process—>dup_task_struct 在dup_task_struct函数中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
	struct task_struct *tsk;
	struct thread_info *ti;
	unsigned long *stackend;

	int err;

	prepare_to_copy(orig);

	tsk = alloc_task_struct();
	if (!tsk)
		return NULL;

	ti = alloc_thread_info(tsk);
	if (!ti) {
		free_task_struct(tsk);
		return NULL;
	}

	err = arch_dup_task_struct(tsk, orig);
	if (err)
		goto out;

	tsk->stack = ti;

	err = prop_local_init_single(&tsk->dirties);
	if (err)
		goto out;

	setup_thread_stack(tsk, orig);
	......

其中alloc_task_struct使用内核的slab分配器去为所要创建的进程分配struct task_struct的空间
而alloc_thread_info使用内核的伙伴系统去为所要创建的进程分配内核栈(union thread_union )空间

注意:

后面的tsk->stack = ti;语句,这就是关联了struct task_struct和内核栈 而在setup_thread_stack(tsk, orig);中,关联了内核栈和struct task_struct:

1
2
3
4
5
static inline void setup_thread_stack(struct task_struct *p, struct task_struct *org)
{
	*task_thread_info(p) = *task_thread_info(org);
	task_thread_info(p)->task = p;
}

内核栈的大小

由于是每一个进程都分配一个内核栈空间,所以不可能分配很大。这个大小是构架相关的,一般以页为单位。其实也就是上面我们看到的THREAD_SIZE, 这个值一般为4K或者8K。对于ARM构架,这个定义在Thread_info.h (arch\arm\include\asm),

1
2
3
#define THREAD_SIZE_ORDER  1
#define THREAD_SIZE   8192
#define THREAD_START_SP   (THREAD_SIZE - 8)

所以ARM的内核栈是8KB 在(内核)驱动编程时需要注意的问题: 由于栈空间的限制,在编写的驱动(特别是被系统调用使用的底层函数)中要注意避免对栈空间消耗较大的代码,比如递归算法、局部自动变量定义的大小等等