Linux Primer

Dive into linux kernel

How to Parse Linux Kernel Parameters

| Comments

每一次深入一个技术问题,随着挖掘的深入,都发现其背后总有非常深的背景知识,甚至需要深入很多底层系统,这个过程有时会让自己迷失,会让自己忘记了当初的目的。

前言

在讨论内核如何解析内核参数之前,我们先了解一下如何给内核参数。详细的内核参数的使用说明,请参加内核文档 Documentation/kernel-parameters.txt,这里我简单总结如下:

假如有如下的cmdline传递给内核

mem=900M console=ttyS0,115200 foo=1 bar ramoops.size=0x8000 earlyprintk -- msg=hello test
  1. 内核只解析--字符以前的参数。
  2. --字符以后的参数,都会传递给init进程。
  3. 如果内核遇到不能识别的参数时,且该参数中没有包含字符.,内核将会把它传递给init进程,如果参数中有=字符时,内核会将该参数传递给init进程作为其环境变量。
  4. 模块可以通过两种方式传递参数:一个是命令行,一个是modprobe。
     (kernel command line) usbcore.blinkenlights=1
     (modprobe command line) modprobe usbcore blinkenlights=1
    
  5. 编译进内核的模块,需要使用命令行的方式给其传递参数。
  6. modprobe在加载内核模块时,会去查找/proc/cmdline来收集模块的参数,所以未编译进内核的可加载的模块也可以使用命令行来传递参数。
  7. 内核参数中,中划线-和下划线_时等价的,例如,如下的两个参数时等价的
     log_buf_len=1M print-fatal-signals=1
     log-buf-len=1M print_fatal_signals=1
    
  8. 如果参数的值中有空格,可以使用双引号将其包住,例如:
     param="spaces in here"
    

第一个阶段

第二个阶段

第三个阶段

Sys Rq in Linux Kernel

| Comments

曾几何时,笔者在使用ubuntu系统时,内核卡死,然后就束手无策了。只能去把电源来重启系统,但这是非常危险的,有可能会造成数据的丢失或者磁盘的损害。

幸好,使用sysrq组合键可以让系统安全的进行重启,并能收集一些系统的信息,方便问题的定位。

在终端上同时按Alt, SysRq和命令键则会执行SysRq命令, SysRq键就是”Print Screen”健. 比如Alt+SysRq+b则重启机器。ALT+SysRQ+X(此处X代表命令参数)这是一组“魔术组合键”,只要内核没有被完全锁住,不管内核在做什么事情,使用这些组合键能即时打印出内核的信息。

使用sysrq组合键是了解系统目前运行情况的最佳方式。如果系统出现挂起(例如:死机等)的情况或在诊断一些和内核相关比较难解决的问题的时候,使用sysrq键是个比较好的方式。

在有些系统中,默认SysRq组合键是关闭的。 打开这个功能,运行:

# echo 1 > /proc/sys/kernel/sysrq

关闭这个功能:

# echo 0 > /proc/sys/kernel/sysrq

如果想让此功能一直生效,在/etc/sysctl.conf里面设置kernel.sysrq的值为1. 重新启动以后,此功能将会自动打开。

kernel.sysrq = 1

因为打开sysrq键的功能以后,有终端访问权限的用户将会拥有一些特别的功能。因此,除非是要调试,解决问题,一般情况下,不要打开此功能。如果一定要打开,请确保你的终端访问的安全性。

有几种方式能触发sysrq事件。在带有AT键盘的一般系统上,在终端上输入一下组合键:

Alt+PrintScreen+[CommandKey]

如果你在机器上有root权限,你能把commandkey字符写入到 /proc/sysrq-trigger 文件。这能帮助你通过脚本或你不在系统终端上的时候触发sysrq事件。

# echo 'm' > /proc/sysrq-trigger

哪些类型的sysrq事件能被触发?请参见文档:https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/plain/Documentation/sysrq.txt

下面具体介绍一些如何安全的重启卡死的linux系统

使用 SysRq 重启计算机的方法:

台式机键盘或者全尺寸键盘: Alt + SysRq + [R-E-I-S-U-B]

部分笔记本键盘: Fn + Alt + SysRq + [R-E-I-S-U-B]

解释:括号内的英文字母需要依次顺序按下,而且每次按下字母后需要间隔 5-10s 再执行下一个动作。(如 alter +SysRq + R,间隔10s 后再按 alter+ SysRq +E,以此类推)切记不可快速按下 R-E-I-S-U-B ,否则后果和 扣电池拔电源线无异!

下面详细讲解一下各个序列:

  • unRaw – 把键盘设置为 ASCII 模式,使按键可以穿透 x server 捕捉传递给内核
  • tErminate – 向除 init 外进程发送 SIGTERM 信号,让其自行结束
  • kIll - 向除 init 以外所有进程发送 SIGKILL 信号,强制结束进程
  • Sync – 同步缓冲区数据到硬盘,避免数据丢失
  • Unmount – 将所有已经挂载的文件系统 重新挂载为只读
  • reBoot - 立即重启计算机

How to Build Linux Kernel in Ubuntu Faster?

| Comments

传统编译方式

通常,我们要需要编译linux内核,大概要经历以下几个步骤:

配置内核

make menuconfig

编译内核和模块

依次执行如下命令:

make
make modules
make modules_install
make install

使用make-kpkg

如果是ubuntu的用户,可以使用make-kpkg简化这个流程,而且还能带来额外的好处。 在ubuntu下,安装kernel-package这个包之后,就可以使用make-kpkg了。

sudo apt-get install kernel-package

使用make-kpkg编译内核,第一个步骤“配置内核是必不可少的”,在这里,我比较建议在发行版默认的config的基础上再进行配置,这样配置出来的内核和发行版本身具有更好的相容性。比如ubuntu 14.10,可以在运行make menuconfig之前执行命令cp /boot/config-3.13.0-44-generic .config

配置完内核后,接下来执行真正的编译过程。通常我们可以执行如下命令:

make-kpkg  --initrd -j 8 --append-to-version -20150213001 kernel_image
  • –initrd选项会让make-kpkg自动帮助我们生成initramfs
  • –revisin会让生成的deb文件加上一个版本信息,这个参数只影响到文件名。
  • –append-to-version 也是一种版本信息,它不仅会出现在deb安装包的名称中,也会影响到kernel的名称。
  • kernel_image表示生成内核和默认模块的安装包,另外您也可以加上kernel_headers,这样也会生成一个内核头文件的安装包。

编译过程执行完毕之后,会在上层目录里生成一个deb安装包。

使用make-kpkg来编译内核,还有其他好处。因为我们是通过包管理器来安装新的内核,当不再需要这个内核时,就可以简单的通过dpkg命令、新立得软件包管理器或者Ubuntu软件中心来完全卸载,而不需要一个个手动删除修改。

如果需要详细了解make-kpkg的用法,可以查阅manual:

man make-kpkg

TIPS

默认的config会编译很多模块,为了尽可能少的编译模块,在配置内核时,可以使用如下命令精简内核模块:

make localmodconfig

Linux 时钟和定时器

| Comments

时钟

时钟这个东西,实际上是作为一种工具而存在,内核通过时钟来感知、管理时间。这里的时钟,更主要的还是软件上的概念,系统通过维护软件时钟来追踪时间。

概念

  1. 时钟中断:由硬件产生的电信号,一切的缘起。该中断产生时,内核通过特殊的中断处理程序进行处理。
  2. 节拍率(tick rate):系统以某种频率(可编程)自行触发(hitting、popping)时钟中断(即系统定时器的频率)。
  3. 节拍(tick):由于节拍率已知,系统当然也知道两次时钟中断之间所间隔的时间,这个时间就是时钟节拍。

再说节拍率:HZ

节拍率,即系统定时器的频率,在内核中通过HZ这个宏进行定义。在进行内核编程的时候,切记不要假设HZ不会发生变化,事实上,大多数体系结构的HZ都是可调的。

HZ的理想取值:从2.5内核开始,这个取值在i386体系结构中就改为了1000(2.6.13版本后的内核,加入了250这个取值)。改变HZ的取值,对于操作系统而言,意味着改变时钟中断的频率:

增大HZ:提高时钟中断的频率,这带来的好处是,提高了时间驱动事件的解析度与精确度,内核定时器具有更高的频度与精确度(依赖内核定时器的系统调用也有了更精确的执行度,比如select、epoll等,这会带来很大的性能提升),时间相关的测量会更准确,内核抢占更准确,进程调度的响应更及时。

当然也会有负面影响:更高的中断频率,必然会导致系统消耗更多的资源来处理时钟中断(当然,就目前的主机来说,1000还是一个不错的取值)。

2.6的内核允许在编译的时候,选择不同的HZ取值,用户空间的USER_HZ,是根据内核的HZ进行了相应的转换。

最后顺便说一下,OS也是可以采取无节拍的实现的,但系统的开销会非常大。

jiffies

变量类型为unsigned long volatile,该变量记录了系统启动以来,产生的tick总数,系统运行时间 = jiffies/HZ

下面是几个典型运用:

1
2
3
unsigned long timestamp = jiffies
unsigned long nextTick = jiffies + 1
unsigned long 5sLater = jiffies + 5*HZ

Linux世界里的时间

| Comments

通常,操作系统可以使用三种方法来表示系统的当前时间与日期:

  1. 简单的一种方法就是直接用一个64位的计数器来对时钟滴答进行计数。
  2. 第二种方法就是用一个32位计数器来对进行计数,同时还用一个32位的辅助计数器对时钟滴答计数,直至累积到一秒为止。因为232超过136年,因此这种方法直至22世纪都可以让系统工作得很好。
  3. 第三种方法也是按时钟滴答进行计数,但是是相对于系统启动以来的滴答次数,而不是相对于某个确定的外部时刻;当读外部后备时钟(如RTC)或用户输入实际时间时,根据当前的滴答次数计算系统当前时间。

基本概念

首先,有必要明确一些Linux内核时钟驱动中的基本概念。

时钟周期的频率

时钟周期(clock cycle)的频率:8253/8254 PIT的本质就是对由晶体振荡器产生的 时钟周期进行计数,晶体振荡器在1秒时间内产生的时钟脉冲个数就是时钟周期的频率

Linux用宏CLOCK_TICK_RATE来表示8254 PIT的输入时钟脉冲的频率,该宏定义在arch/x86/include/asm/timex.h头文件中:

1
2
./arch/x86/include/asm/timex.h:8:#define CLOCK_TICK_RATE  PIT_TICK_RATE
./include/linux/timex.h:153:#define PIT_TICK_RATE 1193182ul

时钟滴答

时钟滴答(clock tick):当PIT通道0的计数器减到0值时,它就在IRQ0上产生一次时钟中断,也即一次时钟滴答。PIT通道0的计数器的初始值决定了要过多少时钟周期才产生一次时钟中断,因此也就决定了一次时钟滴答的时间间隔长度。

时钟滴答的频率

时钟滴答的频率(HZ):即1秒时间内PIT所产生的时钟滴答次数。类似地,这个值也是由PIT通道0的计数器初值决定的(反过来说,确定了时钟滴答的频率值后也就可以确定8254 PIT通道0的计数器初值)。

Linux内核用宏HZ来表示时钟滴答的频率,而且在不同的平台上HZ有不同的定义值。对于ALPHA和IA62平台HZ的值是1024,对于SPARC、MIPS、ARM和i386等平台HZ的值都是100。该宏在i386平台上的定义如下:

1
2
3
#ifndef HZ 
#define HZ 100 
#endif 

根据HZ的值,我们也可以知道一次时钟滴答的具体时间间隔应该是(1000ms/HZ)=10ms。

Which 命令分析

| Comments

环境

Linux 3.2.0-40-generic #64-Ubuntu SMP Mon Mar 25 21:22:10 UTC 2013 x86_64 x86_64 x86_64 GNU/Linux

which 命令的位置

1
2
3
datawolf@datawolf-ThinkPad-Edge:~$ which -a which
/usr/bin/which
/bin/which

分析两个which文件的类型

通过以下shell命令可以看出,/usr/bin/which是一个指向/bin/which的符号链接。而/bin/which是一个shell脚本。

1
2
3
4
5
6
7
datawolf@datawolf-ThinkPad-Edge:~$ ls -al  /usr/bin/which /bin/which
-rwxr-xr-x 1 root root 946 Mar 30  2012 /bin/which
lrwxrwxrwx 1 root root  10 Feb 21 16:23 /usr/bin/which -> /bin/which
datawolf@datawolf-ThinkPad-Edge:~$ file /usr/bin/which
/usr/bin/which: symbolic link to `/bin/which'
datawolf@datawolf-ThinkPad-Edge:~$ file /bin/which
/bin/which: POSIX shell script, ASCII text executable

自动备份网站和数据库的shell脚本

| Comments

备份网站内容

我的网站是用xampp集成环境搭建的。为了方便迁移和备份数据。使用如下脚本对我网站内容进行备份。其中,BACKUPDIR指定备份文件的存储位置。 每一次备份的文件都用备份时系统的时间来进行唯一的命名,获取实际使用命令date -I

1
2
3
4
5
6
7
8
9
#!/bin/bash

# where to store the backup file
BACKUPDIR="/home/wanglong"

DATE=`date -I`
[ ! -d $BACKUPDIR ] && mkdir -p $BACKUPDIR
cd /opt/lampp
tar  zcf $BACKUPDIR/htdocs.$DATE.tar.gz htdocs

备份mysql数据库

备份数据库前,需要指定一下shell变量,包括mysql数据库的用户名、密码、运行数据库的主机和数据库相关命令的位置。

1
2
3
4
5
6
MYUSER="user"
MYPASS="password"
HOST="localhost"
BACKUPDIR="/home/wanglong"
MYSQL="/opt/lampp/bin/mysql"
MYSQL_DUMP="/opt/lampp/bin/mysqldump"

完整shell脚本如下,首先将所有的数据库作为一个整体进行备份,然后对每一个数据库分别进行了备份。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/bin/bash

MYUSER="user"
MYPASS="password"
HOST="localhost"
BACKUPDIR="/home/wanglong"
MYSQL="/opt/lampp/bin/mysql"
MYSQL_DUMP="/opt/lampp/bin/mysqldump"
DATE=`date -I`

[ ! -d $BACKUPDIR/$DATE ] && mkdir -p $BACKUPDIR/$DATE

# backup all databases in a file
$MYSQL_DUMP -u$MYUSER -p$MYPASS -h$HOST  --all-databases > $BACKUPDIR/$DATE/all-databases.sql

# backup each database in separate file
DBS=`$MYSQL -u$MYUSER -p$MYPASS -Bse "show databases"|grep -v "information_schema" | grep -v "performance_schema"|grep -v "Database"`
for db_name in $DBS
  do
    $MYSQL_DUMP  -u$MYUSER -p$MYPASS -h$HOST  $db_name  > $BACKUPDIR/$DATE/$db_name.sql
  done

cd $BACKUPDIR
tar zcf mysql.$DATE.tar.gz $DATE && rm -rf $BACKUPDIR/$DATE

自动执行脚本

计划每周自动备份一次网站和数据库,所以需要将上述脚本拷贝到目录/etc/cron.weekly/中。

Using Bat File to Run Python Program on Windows

| Comments

在windows下批处理文件中调用python

由于python的变量的问题,不能在批处理文件somefile.bat正确使用python somefile.py来执行python程序。

所以需要在bat文件中临时修改python环境变量。

最终的bat文件如下:

1
2
3
4
5
6
7
8
9
10
@echo off
set PYPATH=C:\Python27;C:\Python27\Lib;C:\Python27\libs;C:\Python27\Tools\Scripts;
set path=%PYPATH%;%path%
@echo on

python get_all_problems.py
python get_need_solved.py
python sort_problems.py

pause

Ubuntu下安装eclipse的Could Not Load SWT Library问题

| Comments

Description

版本:Ubuntu 12.04, Eclipse3.7.2-1, Oracle Java 1.6

1
$ sudo apt-get install eclipse

Question

安装后打开eclipse,提示出错

1
An error has occurred. See the log file /home/datawolf/.eclipse/org.eclipse.platform_3.7.0_155965261/configuration/1365058671830.log.

打开log文件,看到下面的错误

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
39
!SESSION 2013-04-04 14:57:51.666 -----------------------------------------------
eclipse.buildId=I20110613-1736
java.version=1.6.0_37
java.vendor=Sun Microsystems Inc.
BootLoader constants: OS=linux, ARCH=x86_64, WS=gtk, NL=en_US
Command-line arguments:  -os linux -ws gtk -arch x86_64

!ENTRY org.eclipse.osgi 4 0 2013-04-04 14:57:53.033
!MESSAGE Application error
!STACK 1
java.lang.UnsatisfiedLinkError: Could not load SWT library. Reasons: 
  no swt-gtk-3740 in java.library.path
  no swt-gtk in java.library.path
  Can't load library: /home/datawolf/.swt/lib/linux/x86_64/libswt-gtk-3740.so
  Can't load library: /home/datawolf/.swt/lib/linux/x86_64/libswt-gtk.so

  at org.eclipse.swt.internal.Library.loadLibrary(Library.java:285)
  at org.eclipse.swt.internal.Library.loadLibrary(Library.java:194)
  at org.eclipse.swt.internal.C.<clinit>(C.java:21)
  at org.eclipse.swt.internal.Converter.wcsToMbcs(Converter.java:63)
  at org.eclipse.swt.internal.Converter.wcsToMbcs(Converter.java:54)
  at org.eclipse.swt.widgets.Display.<clinit>(Display.java:132)
  at org.eclipse.ui.internal.Workbench.createDisplay(Workbench.java:695)
  at org.eclipse.ui.PlatformUI.createDisplay(PlatformUI.java:161)
  at org.eclipse.ui.internal.ide.application.IDEApplication.createDisplay(IDEApplication.java:153)
  at org.eclipse.ui.internal.ide.application.IDEApplication.start(IDEApplication.java:95)
  at org.eclipse.equinox.internal.app.EclipseAppHandle.run(EclipseAppHandle.java:196)
  at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
  at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
  at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:344)
  at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
  at java.lang.reflect.Method.invoke(Method.java:597)
  at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:622)
  at org.eclipse.equinox.launcher.Main.basicRun(Main.java:577)
  at org.eclipse.equinox.launcher.Main.run(Main.java:1410)
  at org.eclipse.equinox.launcher.Main.main(Main.java:1386)

Solution

把相关文件拷贝到~/.swt/lib/linux/x86_64下即可

1
$ cp /usr/lib/jni/libswt-*3740.so ~/.swt/lib/linux/x86_64

How to Install Sun Jdk 6 on Ubuntu Linux

| Comments

下载相应的版本

下载地址下载相应的版本,32位就x86,64位就x64,我下载的是jdk-6u37-linux-x64.bin;

安装

1、创建安装目录

1
2
$ sudo mkdir /usr/java
$ cd /usr/java/

2、拷贝下载好的jdk到安装目录

1
$ sudo mv ~/jdk-6u37-linux-x64.bin ./

3、安装

1
$ sudo ./jdk-6u37-linux-x64.bin

安装完后,会在/usr/java目录下多出一个jdk_1.6.0_37的目录。

配置

1、配置环境变量

编辑/etc/bashrc,添加如下内容

1
2
3
4
JAVA_HOME=/usr/java/jdk1.6.0_37
PATH=$PATH:$JAVA_HOME/bin
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$CLASSPATH
export JAVA_HOME PATH CLASSPATH

2、使环境变量生效

1
$ source /etc/bashrc

3、替换ubuntu默认的jdk

1
2
3
4
5
6
7
8
$ sudo update-alternatives --install /usr/bin/java  java  /usr/java/jdk1.6.0_37/bin/java 999
$ sudo update-alternatives --install /usr/bin/javac  javac  /usr/java/jdk1.6.0_37/bin/javac 999
$ sudo update-alternatives --install /usr/bin/javadoc javadoc /usr/java/jdk1.6.0_37/bin/javadoc 999

#以下三个命令会让你选择,选择刚安装好的版本就行了
$ sudo update-alternatives --config java
$ sudo update-alternatives --config javac
$ sudo update-alternatives --config javadoc

4、检查一下,是否正确

1
$ ls -lh /etc/alternatives/java*

结果如下:

1
2
3
lrwxrwxrwx 1 root root 30 Apr  3 17:27 /etc/alternatives/java -> /usr/java/jdk1.6.0_37/bin/java
lrwxrwxrwx 1 root root 31 Apr  3 17:28 /etc/alternatives/javac -> /usr/java/jdk1.6.0_37/bin/javac
lrwxrwxrwx 1 root root 33 Apr  3 17:28 /etc/alternatives/javadoc -> /usr/java/jdk1.6.0_37/bin/javadoc