PS: PR 也有人說 Trial Run / Control Run / Ramp Up
各階段產品:
* ES (Engineer Sample): 用來確認產品基本功能,不保證實用性、耐用度
* CS (Customer Sample): 功能都要能正常工作,不能當機、掛點、失效
* MP (Mass Production): 消費者手上拿到的量產版
測試驗證:
* EVT (Engineering Verification Test)
* DVT (Design Verification Test)
* PVT(Production Verification Test)
最後就是 EOL(End Of Life)
另外一些資訊業常見到的術語:
* YTD / QTD / MTD (Year To Date / Quarter To Date / Month To Date) - 年初/季初/月初 到現在
* R&D (Research and Development) - 通常指研究開發的工作或人員
* Design House - 即一般常說的IC廠,指的是以設計晶片為主要業務的公司,如 Intel、Qualcomm、聯發科等公司。
* OEM (Original Equipment Manufacturer) - 以品牌銷售為主的公司,如 Asus、Acer、Dell、HP 等
* ODM (Original Design Manufacturer) - 指的是提供代工服務,包括軟硬體、工業設計服務
* AE (Application Engineer) - 指的是 Design House 協助RD端的應用工程師,有的公司稱為SA。
* FAE (Field Application Engineer/Filed Assist Engineer) - 相較於AE,FAE 通常偏向問題管理,有的公司是負責市場端的銷售服務,有些則是負責技術。
* TAM (Technical Account Manager) - 技術客戶經理,類似 FAE 的工作,主要負責客戶的技術相關問題。
* POR (Plan Of Record) - IC廠的 chip 會有技術規格,提供 chip 的功能。POR 指的是這些功能是否有計劃支援。
* IBV (Independent BIOS Vendor) - BIOS供應商,如 AMI、Phoenix、Insyde 和 Byosoft
* R&R (Role and Responsibility) -
今年中出的 Ubuntu 11.10 把 gcc 升級到 4.6,這會讓不少人的 Android build 不出來 :X
主要的原因很簡單,因為大家的 code 不夠嚴謹,跟不上 gcc 4.6。這通常都是BSP本身的問題,因為gcc版本
影響的是 host 端,最大宗的就是廠商提供的 host tool、flash tool
如果真的要用 11.10 去 build Android最簡單的方式就是把 gcc 切回 4.4
先確認有裝 gcc 相關的檔案(g++, libs ... etc.)都是 4.4,可以查 /usr/bin/gcc-4.4 和 /usr/bin/g++-4.4 是否存在
sudo apt-get install gcc-4.4 g++-4.4 gcc-4.4-multilib g++-4.4-multilib
然後利用 update-alternative 把4.6 切成 4.4 即可:
$sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.4 50 --slave /usr/bin/g++ g++ /usr/bin/g++-4.4 --slave /usr/bin/gcov gcov /usr/bin/g++-4.4
$sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.6 40 --slave /usr/bin/g++ g++ /usr/bin/g++-4.6 --slave /usr/bin/gcov gcov /usr/bin/g++-4.6
$sudo update-alternatives --config gcc
選擇 4.4
]]>其實 Google 開發中的平台可以從 Android 的 gitweb 和 review board 看出一些端倪,包括 CPU、code name、採用的 kernel function ... etc.
以下是曾經個人追過的 device,括號內為Google 或 SoC 廠開發時使用的 code name (2.0之前,目前回去看也沒啥意義,就不列了 :P):
2.1~2.3 (QSD8x50、Samsung S5P110)
Google Nexus one (mahimahi, 2.1~2.3):
* 2.1: archive/android-msm-2.6.29-nexusone
* 2.2: archive/android-msm-2.6.32
* 2.3: android-msm-2.6.35
Google Nexus S (herring, 2.3~):
* 2.3: android-samsung-2.6.35
3.0 ~ 3.2 (Nvidia Tegra2)
Motorola Xoom (stingray):
* 3.0: android-tegra-2.6.36-honeycomb
* 3.1: android-tegra-2.6.36-honeycomb-mr1
Nvidia develop board (Ventana, Whistler, Harmony)
* dev-hc
4.0 (TI OMAP 4430 + Kernel 3.0)
Nexus 3 ? (panda)
* 4.0 android-omap-3.0
Qualcomm develop board:
Qualcomm 的架構相較其他平台較完整,所以他們的 code base、development board 都是在相同一份 code base 即可以找到
目前市面上所有的平台包括: 7x30, 8x50, 8x55, 8x60 ... etc. 都可在其開發網站 (Cod Aurora) 找到
https://www.codeaurora.org/gitweb/quic/la/?p=kernel/msm.git;a=summary
Reference:
* Android 版本: http://zh.wikipedia.org/wiki/Android
* Android review board: https://review.source.android.com/#/q/status:open,n,z
* Android Git web: http://android.git.kernel.org/
* Linux Kernel: http://kernel.org/
Vold 的trace 可以參考下面文章的分析,trace 跟原理都寫得很詳細。
http://blog.csdn.net/datangsoc/article/details/5928132
PS: 最早的 Android 並沒有 vold,而是更單純的 mountd
udev/vold 的架構簡單來說就是,kernel 會在新增 device 時,透過socket 發出 uevent 到 userspace, vold 則是負責去接收這些 event 並且做出相對應的處理。
Google 之所以不在 Android 採用 udev 的原因,可能是因為早期的 Android 最多也只能插入 SD card,所以 vold 內,主要負責的是 SD card 的處理。
在 Android Tablet 推出後,許多廠商加入了標準 USB port,這樣一來,可以使用的 device 就更多了,只是由於缺乏 udev的機制,實作上就會比較混亂。
參考文章:
* Volume service
* udev 官網: http://www.kernel.org/pub/linux/utils/kernel/hotplug/udev.html
* udev - 強大的 device node 管理系統
* uevent 分析
* udev rules
* mastering udev
當機器上有打開 USB debug mode 時,使用者即可通過adb 進行各種 debug 、底層(linux user space)的 Android 功能。比較常用的功能:
- tools\ddms.bat: Android AP/Framework 層最主要的 debug tool (已經改名為)
- 安裝 Android 應用程式 (*.apk)
- 連接機器,使用 linux userspace 的功能。 ex: ping, ssh, ftp ... blah blah.
這篇文章主要是整理了一些adb 的基本功能,後面補上一些開發時常用的功能。
]]>
可以在官方的 Android init language 文件 (system/core/init/readme.txt)找到
]]>
Digital media file transfer The platform includes built-in support for Media/Picture Transfer Protocol (MTP/PTP) over USB, which lets users easily transfer any type of media files between devices and to a host computer. Developers can build on this support, creating applications that let users create or manage media files that they may want to transfer or share across devices.
MTP/PTP簡單來說就是支援多媒體傳輸的協定,同時支援一般的 UMS 功能。這兩項功能分別由兩間廠商所提出,MTP 是微軟為了搭配 Windows Media Player 所提出的、而PTP由 Kodak 為了數位照片所提出。市場上,其實 Samsung 早就在 Galaxy S 就使用 MTP 取代傳統的 UMS,而 Nokia 在 Symbian 也有 support MTP。
Google 在 Android 3.0 會採用 MTP 作為標準,其實有一大部分是為了DRM (Digital Rights Management) 而作的,因為MTP原本就是為了 DRM 所設計出來的協定。如同MS當初提出MTP的原因,Google目前面臨的也是如何處理數位內容的問題,才能替Android 取得更多的數位內容,其實更是為了解決 Android App 太容易被散佈問題鋪路。
更詳細的 MTP 實作原理可以參考此 文件 ,概要來說
- MTP 實作分為物理層(Physical Layer)、傳輸層(Transport Layer)以及指令層(Command Layer)
- MTP 是基於 PTP 設計出來的,兩者在物理層的運作方式是相同的
- MTP 增加了對播放清單、PDA、日曆、聯絡人、群組影像 等功能的支援。
- 加入數位版權管理(DRM)屬性,包括DRM狀態、URL(在授權權限過期時,讓DRM可以要求付費)、使用次數和其他相關屬性。
- 支援超過4G的檔案傳輸
- 基本MTP規格的使用是免費的,但只要使用MTP就必須完整支援其規格
- Enhanced Initiator (主機端 ?)則是要付費的
Google 在 Android 3.0 實作的 MTP 可在 2.6.36 的 tegra kernel 找到
下面是 MTP 的一些基本資訊的參考來源:
Introduction to MTP: Media Transfer Protocol 譯文:
http://shiwawa.blog.hexun.com.tw/8323038_d.html
MTP Function driver:
http://www.usb-software.org/mtp_function_driver.php
點進去可能會被複雜度嚇到就是了 :P
http://www.makelinux.net/kernel_map
覺得太複雜的,可以先從新手版看起 XD
其實手機的硬體佈線和使用元件是很有趣的,可以看出很多端倪 ...
Nexus S teardown:
- http://www.ifixit.com/Teardown/Nexus-S-Teardown/4365/1
Galaxy S (GT-9000) teardown:
- http://www.phonewreck.com/2010/08/19/samsung-galaxy-s-teardown/
- http://www.eettaiwan.com/ART_8800625414_675327_NT_ca7a0132.HTM
- http://www.cn.fpdisplay.com/Forecast/Article.aspx?id=a82137de-2eca-4628-9cb0-c943ab4591f2
- http://www.phonewreck.com/wp-content/uploads/2010/08/GSBD11.jpg
iPhone 4 teardown
http://www.ifixit.com/Teardown/iPhone-4-Teardown/3130/1
重要元件:
CPU - S5PC110A01 1GHz Cortex A8 Hummingbird Processor
Samsung 的CPU,業界應該都知道他跟 IPhone4 的A4 應該是同樣的架構
MCP:
Nexus: KB100D00WM-A453 包含 8Gb的OneNand (KFG8GH6Q4M)、2Gb的DDR SDRAM (K4X2G323PB)、1Gb DDR SDRAM (K4X1G323B)、還有一顆不明元件。
Flash:
Nexus S: SanDisk SDIN4C2 16GB MLC NAND flash
iPhone4: Samsung K9PFG08
Galaxy S: Samsung KLM8G4DEDD moviNAND
Baseband Processor
Nexus S: Infineon 8824 XG616 X-Gold
Galaxy S: Infineon YYN1N7438A8 (X-GOLD 61x ?)
IPhone 4: Infineon X-GOLD 61x
E-Compass
iPhone4: AKM8975
Touch
Nexus S: Atmel MXT224
iPhone4: TI 343S0499 Touch Screen Controller
Android related:
EXT4
- http://kernelnewbies.org/Ext4
FS benchmark
- Ext2 v.s. Ext3 v.s. Ext4 性能比拚
- Ext4 ReiserFS Btrfs 等七種文件系統性能比拚
- IOzone for filesystem performance benchmarking
Partition identifiers for PC:
- http://www.win.tue.nl/~aeb/partitions/partition_types-1.html
eMMC
eMMC 各家廠商都有自己名字, 這點還蠻有趣的, Samsung 的 eMMC 叫 "moviNAND", Sandisk 叫 "iNAND", Hynix 叫 "eNAND" (Ref)
eMMC 和 傳統nand flash 最大的差異點在 eMMC 內建 controller,且由其負責wear leveling 而傳統 nand flash 則是由 file system 負責
]]>但這是立基於SoC provider 的所提供的 API,並不具備一個標準的介面。而且在嵌入式系統大力發展下,越來越多的
SoC 推出,加上SoC要連接越來越多的周邊,會使用FPGA或是 I/O expander 來擴充 GPIO 數量。因此除了SoC
本身的介面之外,開發者還必須藉由另外一套或更多的介面來使用 GPIO,這種沒有共通標準的東西勢必會造成開發上的
一些困擾,所以這幾年 linux 發展出一套新的 gpiolib 來解決這問題。作者 David Brownell 的patch 是這樣敘述的:
gpiolib: add gpio provider infrastructure
Provide new implementation infrastructure that platforms may choose to use
when implementing the GPIO programming interface. Platforms can update their
GPIO support to use this. In many cases the incremental cost to access a
non-inlined GPIO should be less than a dozen instructions, with the memory
cost being about a page (total) of extra data and code. The upside is:
* Providing two features which were "want to have (but OK to defer)" when
GPIO interfaces were first discussed in November 2006:
- A "struct gpio_chip" to plug in GPIOs that aren't directly supported
by SOC platforms, but come from FPGAs or other multifunction devices
using conventional device registers (like UCB-1x00 or SM501 GPIOs,
and southbridges in PCs with more open specs than usual).
- Full support for message-based GPIO expanders, where registers are
accessed through sleeping I/O calls. Previous support for these
"cansleep" calls was just stubs. (One example: the widely used
pcf8574 I2C chips, with 8 GPIOs each.)
* Including a non-stub implementation of the gpio_{request,free}() calls,
making those calls much more useful. The diagnostic labels are also
recorded given DEBUG_FS, so /sys/kernel/debug/gpio can show a snapshot
of all GPIOs known to this infrastructure.
The driver programming interfaces introduced in 2.6.21 do not change at all;
this infrastructure is entirely below those covers.
gpiolib 雖於 2008 的2.6.21 就引進,但在Android 上開始使用大約是2.6.32 的版本,因此建議 Kernel 版本最好是
2.6.32 以上。
首先還是讀一下 kernel document 了解概況,其位置在 kernel/Documentation/gpio.txt。
gpiolib 的實作則是在drivers/gpio/gpiolib.c
- 待續 -
使用注意:
由於一般多是使用 i2c 或其他的 bus 去控制這些 gpio expander,所以必須考慮到的東西相對就多了,例如 sleep 就
有相對應的:
- gpio_set_value_cansleep()/gpio_get_value_cansleep() 這組 function
參考文件:
]]>來源: http://blog.chinaunix.net/u3/111072/showart_2273346.html
]]>
主要問題牽扯到 2.6 引進的 initramfs 處理 ramdisk 的流程,還要先了解 bootloader 的
一些背景知識。找了些相關資訊,順便做做筆記:
initramfs 相關的 src path:
- init/initramfs.c
- arch/arm/mm/init.c
initrd vs. initramfs
- Jserv's blog: 深入理解 Linux 2.6 的 initramfs 機制 (上)
- Poorman 的雜記:initramfs 簡介,一個新的 initial RAM disks 的模型
- Introducing initramfs, a new model for initial RAM disks
- Jollen 的 Blog: 簡易的 initramfs 製作方式
- initramfs 的簡介,一個新的inital RAM disks 模型
出問題的點在 unpack_to_rootfs 時 gunzip 發生 crc error,所以又 ref 了一些相關資訊:
]]>system/core/include/private/android_filesystem_config.h
包括 root, system, :
#define AID_ROOT 0 /* traditional unix root user */
#define AID_SYSTEM 1000 /* system server */
#define AID_RADIO 1001 /* telephony subsystem, RIL */
#define AID_BLUETOOTH 1002 /* bluetooth subsystem */
#define AID_GRAPHICS 1003 /* graphics devices */
#define AID_INPUT 1004 /* input devices */
#define AID_AUDIO 1005 /* audio devices */
#define AID_CAMERA 1006 /* camera devices */
#define AID_LOG 1007 /* log devices */
#define AID_COMPASS 1008 /* compass device */
#define AID_MOUNT 1009 /* mountd socket */
#define AID_WIFI 1010 /* wifi subsystem */
#define AID_ADB 1011 /* android debug bridge (adbd) */
#define AID_INSTALL 1012 /* group for installing packages */
#define AID_MEDIA 1013 /* mediaserver process */
#define AID_DHCP 1014 /* dhcp client */
#define AID_SDCARD_RW 1015 /* external storage write access */
#define AID_VPN 1016 /* vpn system */
#define AID_KEYSTORE 1017 /* keystore subsystem */
#define AID_FM_RADIO 1018 /* FM radio */
#define AID_SHELL 2000 /* adb and debug shell user */
#define AID_CACHE 2001 /* cache access */
#define AID_DIAG 2002 /* access to diagnostic resources */
/* The 3000 series are intended for use as supplemental group id's only.
* They indicate special Android capabilities that the kernel is aware of. */
#define AID_NET_BT_ADMIN 3001 /* bluetooth: create any socket */
#define AID_NET_BT 3002 /* bluetooth: create sco, rfcomm or l2cap sockets */
#define AID_INET 3003 /* can create AF_INET and AF_INET6 sockets */
#define AID_NET_RAW 3004 /* can create raw INET sockets */
#define AID_NET_ADMIN 3005 /* can configure interfaces and routing tables. */
#define AID_QCOM_ONCRPC 3006 /* can read/write /dev/oncrpc/* files .*/
#define AID_MISC 9998 /* access to misc storage */
#define AID_NOBODY 9999
#define AID_APP 10000 /* first app user */
]]>
kernel/include/asm-generic/errno-base.h
kernel/include/asm-generic/errno.h
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
#define EXDEV 18 /* Cross-device link */
#define ENODEV 19 /* No such device */
#define ENOTDIR 20 /* Not a directory */
#define EISDIR 21 /* Is a directory */
#define EINVAL 22 /* Invalid argument */
#define ENFILE 23 /* File table overflow */
#define EMFILE 24 /* Too many open files */
#define ENOTTY 25 /* Not a typewriter */
#define ETXTBSY 26 /* Text file busy */
#define EFBIG 27 /* File too large */
#define ENOSPC 28 /* No space left on device */
#define ESPIPE 29 /* Illegal seek */
#define EROFS 30 /* Read-only file system */
#define EMLINK 31 /* Too many links */
#define EPIPE 32 /* Broken pipe */
#define EDOM 33 /* Math argument out of domain of func */
#define ERANGE 34 /* Math result not representable */
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Function not implemented */
#define ENOTEMPTY 39 /* Directory not empty */
#define ELOOP 40 /* Too many symbolic links encountered */
#define EWOULDBLOCK EAGAIN /* Operation would block */
#define ENOMSG 42 /* No message of desired type */
#define EIDRM 43 /* Identifier removed */
#define ECHRNG 44 /* Channel number out of range */
#define EL2NSYNC 45 /* Level 2 not synchronized */
#define EL3HLT 46 /* Level 3 halted */
#define EL3RST 47 /* Level 3 reset */
#define ELNRNG 48 /* Link number out of range */
#define EUNATCH 49 /* Protocol driver not attached */
#define ENOCSI 50 /* No CSI structure available */
#define EL2HLT 51 /* Level 2 halted */
#define EBADE 52 /* Invalid exchange */
#define EBADR 53 /* Invalid request descriptor */
#define EXFULL 54 /* Exchange full */
#define ENOANO 55 /* No anode */
#define EBADRQC 56 /* Invalid request code */
#define EBADSLT 57 /* Invalid slot */
#define EDEADLOCK EDEADLK
#define EBFONT 59 /* Bad font file format */
#define ENOSTR 60 /* Device not a stream */
#define ENODATA 61 /* No data available */
#define ETIME 62 /* Timer expired */
#define ENOSR 63 /* Out of streams resources */
#define ENONET 64 /* Machine is not on the network */
#define ENOPKG 65 /* Package not installed */
#define EREMOTE 66 /* Object is remote */
#define ENOLINK 67 /* Link has been severed */
#define EADV 68 /* Advertise error */
#define ESRMNT 69 /* Srmount error */
#define ECOMM 70 /* Communication error on send */
#define EPROTO 71 /* Protocol error */
#define EMULTIHOP 72 /* Multihop attempted */
#define EDOTDOT 73 /* RFS specific error */
#define EBADMSG 74 /* Not a data message */
#define EOVERFLOW 75 /* Value too large for defined data type */
#define ENOTUNIQ 76 /* Name not unique on network */
#define EBADFD 77 /* File descriptor in bad state */
#define EREMCHG 78 /* Remote address changed */
#define ELIBACC 79 /* Can not access a needed shared library */
#define ELIBBAD 80 /* Accessing a corrupted shared library */
#define ELIBSCN 81 /* .lib section in a.out corrupted */
#define ELIBMAX 82 /* Attempting to link in too many shared libraries */
#define ELIBEXEC 83 /* Cannot exec a shared library directly */
#define EILSEQ 84 /* Illegal byte sequence */
#define ERESTART 85 /* Interrupted system call should be restarted */
#define ESTRPIPE 86 /* Streams pipe error */
#define EUSERS 87 /* Too many users */
#define ENOTSOCK 88 /* Socket operation on non-socket */
#define EDESTADDRREQ 89 /* Destination address required */
#define EMSGSIZE 90 /* Message too long */
#define EPROTOTYPE 91 /* Protocol wrong type for socket */
#define ENOPROTOOPT 92 /* Protocol not available */
#define EPROTONOSUPPORT 93 /* Protocol not supported */
#define ESOCKTNOSUPPORT 94 /* Socket type not supported */
#define EOPNOTSUPP 95 /* Operation not supported on transport endpoint */
#define EPFNOSUPPORT 96 /* Protocol family not supported */
#define EAFNOSUPPORT 97 /* Address family not supported by protocol */
#define EADDRINUSE 98 /* Address already in use */
#define EADDRNOTAVAIL 99 /* Cannot assign requested address */
#define ENETDOWN 100 /* Network is down */
#define ENETUNREACH 101 /* Network is unreachable */
#define ENETRESET 102 /* Network dropped connection because of reset */
#define ECONNABORTED 103 /* Software caused connection abort */
#define ECONNRESET 104 /* Connection reset by peer */
#define ENOBUFS 105 /* No buffer space available */
#define EISCONN 106 /* Transport endpoint is already connected */
#define ENOTCONN 107 /* Transport endpoint is not connected */
#define ESHUTDOWN 108 /* Cannot send after transport endpoint shutdown */
#define ETOOMANYREFS 109 /* Too many references: cannot splice */
#define ETIMEDOUT 110 /* Connection timed out */
#define ECONNREFUSED 111 /* Connection refused */
#define EHOSTDOWN 112 /* Host is down */
#define EHOSTUNREACH 113 /* No route to host */
#define EALREADY 114 /* Operation already in progress */
#define EINPROGRESS 115 /* Operation now in progress */
#define ESTAL 116 /* Stale NFS file handle */
#define EUCLEAN 117 /* Structure needs cleaning */
#define ENOTNAM 118 /* Not a XENIX named type file */
#define ENAVAIL 119 /* No XENIX semaphores available */
#define EISNAM 120 /* Is a named type file */
#define EREMOTEIO 121 /* Remote I/O error */
#define EDQUOT 122 /* Quota exceeded */
#define ENOMEDIUM 123 /* No medium found */
#define EMEDIUMTYPE 124 /* Wrong medium type */
#define ECANCELED 125 /* Operation Canceled */
#define ENOKEY 126 /* Required key not available */
#define EKEYEXPIRED 127 /* Key has expired */
#define EKEYREVOKED 128 /* Key has been revoked */
#define EKEYREJECTED 129 /* Key was rejected by service */
/* for robust mutexes */
#define EOWNERDEAD 130 /* Owner died */
#define ENOTRECOVERABLE 131 /* State not recoverable */
Ref.:
USB org: http://www.usb.org/
------------------------------------------
一: 前言
前段時間,一直在寫作業系統和研究Solaris kernel.從而對linux kernel關心甚少.不久前偶然收到富士通的面試,由於諸多原因推辭掉了這次機會.不過招聘要求給我留下了較深的印像.其中涉及到了cgroup機制.cgroup對我來說並不陌生,在LKML上看到過它的path.在2008 AKA大會上也有人對它做為專題分析.不過一直都沒有深入代碼研究.這段時間打算將kernel中新加的功能整理一下,就先從cgroup開始吧.
Cgroup是近代linux kernel出現的.它為process和其後續的子process提供了一種性能控制機制.在這裡不打算對cgroup的作用和使用做過多的描述.本文從linux kernel的源代碼出發分析cgroup機制的相關實現.在本節中,主要分析cgroup的框架實現.在後續的部份再來詳細分析kernel中的幾個重要的subsystem.關於cgroup的使用和介紹可以查看linux-2.6.28-rc7/Documentation/cgroups/cgroup.txt.另外,本文的源代碼分析基於linux kernel 2.6.28版本.分析的源文件基本位於inux-2.6.28-rc7/kernel/cgroup.c和inux-2.6.28-rc7/kernel/debug_cgroup.c中.
二:cgroup中的概念
在深入到cgroup的代碼分析之前.先來瞭解一下cgroup中涉及到的幾個概念:
1:cgroup: 它的全稱為control group.即一組process的行為控制.比如,我們限制process/bin/sh的CPU使用為20%.我們就可以建一個cpu佔用為20%的cgroup.然後將/bin/shprocess添加到這個cgroup中.當然,一個cgroup可以有多個process.
2:subsystem: 它類似於我們在netfilter中的過濾hook.比如上面的CPU佔用率就是一個subsystem.簡而言之.subsystem就是cgroup中可添加刪除的模塊.在cgroup架構的封裝下為cgroup提供多種行為控制.subsystem在下文中簡寫成subsys.
3: hierarchy: 它是cgroup的集合.可以把它理解成cgroup的根.cgroup是hierarchy的結點.還是拿上面的例子: 整個cpu佔用為100%.這就是根,也就是hierarchy.然後,cgroup A設置cpu佔用20%,cgroup B點用50%,cgroup A和cgroup B就是它下面的子層cgroup.
三:cgroup中的重要資料結構
我們先來看cgroup的使用.有三面一個例子:
[root@localhost cgroups]# mount -t cgroup cgroup -o debug /dev/cgroup
[root@localhost cgroups]# mkdir /dev/cgroup/eric_test
如上所示,用debug subsystem做的一個測試. /dev/cgroup是debug subsys的掛載點.也就是我們在上面所分析的hierarchy.然後在hierarchy下又創建了一個名為eric_test的cgroup.
在kernel的源代碼中.掛載目錄,也就是cgroup的根目錄用資料結構struct cgroupfs_root表示.而cgroup用struct cgroup表示.
分別來看一下這兩個結構的含義,struct cgroupfs_root定義如下:
struct cgroupfs_root {
//cgroup文件系統的超級塊
struct super_block *sb;
/*
* The bitmask of subsystems intended to be attached to this
* hierarchy
*/
//hierarchy相關聯的subsys 位圖
unsigned long subsys_bits;
/* The bitmask of subsystems currently attached to this hierarchy */
//當前hierarchy 中的subsys位圖
unsigned long actual_subsys_bits;
/* A list running through the attached subsystems */
//hierarchy中的subsys鏈表
struct list_head subsys_list;
/* The root cgroup for this hierarchy */
//hierarchy中的頂層cgroup
struct cgroup top_cgroup;
/* Tracks how many cgroups are currently defined in hierarchy.*/
//hierarchy中cgroup的數目
int number_of_cgroups;
/* A list running through the mounted hierarchies */
//用來鏈入全局鏈表roots
struct list_head root_list;
/* Hierarchy-specific flags */
//hierarchy的標誌
unsigned long flags;
/* The path to use for release notifications. */
char release_agent_path[PATH_MAX];
};
注意cgroupfs_root中有個struct cgroup結構的成員:top_cgroup.即在每個掛載點下面都會有一個總的cgroup.而通過mkdir創建的cgroup是它的子結點.
其中,release_agent_path[ ]的成員含義.我們在後面再來詳細分析.
Struct cgroup的定義如下:
struct cgroup {
//cgroup的標誌
unsigned long flags; /* "unsigned long" so bitops work */
/* count users of this cgroup. >0 means busy, but doesn't
* necessarily indicate the number of tasks in the
* cgroup */
//引用計數
atomic_t count;
/*
* We link our 'sibling' struct into our parent's 'children'.
* Our children link their 'sibling' into our 'children'.
*/
//用來鏈入父結點的children鏈表
struct list_head sibling; /* my parent's children */
//子結點鏈表
struct list_head children; /* my children */
//cgroup的父結點
struct cgroup *parent; /* my parent */
//cgroup所處的目錄
struct dentry *dentry; /* cgroup fs entry */
/* Private pointers for each registered subsystem */
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
//cgroup所屬的cgroupfs_root
struct cgroupfs_root *root;
//掛載目錄下的最上層cgroup
struct cgroup *top_cgroup;
……
……
}
上面並沒有將cgroup的結構全部都列出來.其它的全部我們等遇到的時候再來進行分析.
其實,struct cgroupfs_root和struct cgroup就是表示了一種空間層次關係,它就對應著掛著點下面的文件示圖.
在上面說過了,cgroup表示process的行為控制.因為subsys必須要知道process是位於哪一個cgroup.
所以.在struct task_struct和cgroup中存在一種映射.
Cgroup在struct task_struct中增加了兩個成員,如下示:
struct task_struct {
……
……
#ifdef CONFIG_CGROUPS
/* Control Group info protected by css_set_lock */
struct css_set *cgroups;
/* cg_list protected by css_set_lock and tsk->alloc_lock */
struct list_head cg_list;
#endif
……
……
}
注意struct task_struct中並沒有一個直接的成員指向cgroup,而是指向了css_set.css_set的結構如下:
struct css_set {
//css_set引用計數
atomic_t refcount;
//哈希指針.指向css_set_table[ ]
struct hlist_node hlist;
//與css_set關聯的task鏈表
struct list_head tasks;
//與css_set關聯的cg_cgroup_link鏈表
struct list_head cg_links;
//一組subsystem states.由subsys->create()創建而成
struct cgroup_subsys_state *subsys[CGROUP_SUBSYS_COUNT];
}
那從css_set怎麼轉換到cgroup呢? 再來看一個輔助的資料結構.struct cg_cgroup_link.它的定義如下:
struct cg_cgroup_link {
/*
* List running through cg_cgroup_links associated with a
* cgroup, anchored on cgroup->css_sets
*/
struct list_head cgrp_link_list;
/*
* List running through cg_cgroup_links pointing at a
* single css_set object, anchored on css_set->cg_links
*/
struct list_head cg_link_list;
struct css_set *cg;
};
如上所示.它的cgrp_link_list鏈入到了cgroup->css_sets. Cg_link_list鏈入到css_set->cg_links.
其中.cg就是批向cg_link_list所指向的css_set.
上面分析的幾個資料結構關係十分複雜.聯繫也十分緊密.下面以圖示的方式直觀將各結構的聯繫表示如下:
注意上圖中的css_set_table[ ].它是一個哈希數組.用來存放struct css_set.它的哈希函數為css_set_hash().所有的衝突項都鏈入數組對應項的hlist.
四:cgroup初始化
Cgroup的初始化包括兩個部份.即cgroup_init_early()和cgroup_init().分別表示在系統初始時的初始化和系統初始化完成時的初始化.分為這兩個部份是因為有些subsys是要在系統剛啟動的時候就必須要初始化的.
4.1: cgroup_init_early()
先看cgroup_init_early()的代碼:
int __init cgroup_init_early(void)
{
int i;
//初始化全局量init_css_set
atomic_set(&init_css_set.refcount, 1);
INIT_LIST_HEAD(&init_css_set.cg_links);
INIT_LIST_HEAD(&init_css_set.tasks);
INIT_HLIST_NODE(&init_css_set.hlist);
//css_set_count:系統中struct css_set計數
css_set_count = 1;
//初始化全局變量rootnode
init_cgroup_root(&rootnode);
//將全局變量rootnode添加到roots鏈表
list_add(&rootnode.root_list, &roots);
root_count = 1;
//使系統的初始化processcgroup指向init_css_set
init_task.cgroups = &init_css_set;
//將init_css_set和rootnode.top_cgroup關聯起來
init_css_set_link.cg = &init_css_set;
list_add(&init_css_set_link.cgrp_link_list,
&rootnode.top_cgroup.css_sets);
list_add(&init_css_set_link.cg_link_list,
&init_css_set.cg_links);
//初始化css_set_table[ ]
for (i = 0; i < CSS_SET_TABLE_SIZE; i++)
INIT_HLIST_HEAD(&css_set_table[i]);
//對一些需要在系統啟動時初始化的subsys進行初始化
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
BUG_ON(!ss->name);
BUG_ON(strlen(ss->name) > MAX_CGROUP_TYPE_NAMELEN);
BUG_ON(!ss->create);
BUG_ON(!ss->destroy);
if (ss->subsys_id != i) {
printk(KERN_ERR "cgroup: Subsys %s id == %d\n",
ss->name, ss->subsys_id);
BUG();
}
if (ss->early_init)
cgroup_init_subsys(ss);
}
return 0;
}
這裡主要是初始化init_task.cgroup結構.伴隨著它的初始化.相繼需要初始化rootnode和init_css_set.接著,又需要初始化init_css_set_link將rootnode.top_cgroup和init_css_set關聯起來.
接著初始化了哈希數組css_set_table[]並且將一些需要在系統剛啟動時候需要初始化的subsys進行初始化.
從上面的代碼可以看到.系統中的cgroup subsystem都存放在subsys[].定義如下:
static struct cgroup_subsys *subsys[] = {
#include <linux/cgroup_subsys.h>
}
即所有的subsys都定義在linux/cgroup_subsys.h中.
對照之前分析的資料結構,應該不難理解這段代碼.下面來分析一下里面所遇到的一些重要的子函數.
Init_cgroup_root()代碼如下:
static void init_cgroup_root(struct cgroupfs_root *root)
{
struct cgroup *cgrp = &root->top_cgroup;
INIT_LIST_HEAD(&root->subsys_list);
INIT_LIST_HEAD(&root->root_list);
root->number_of_cgroups = 1;
cgrp->root = root;
cgrp->top_cgroup = cgrp;
init_cgroup_housekeeping(cgrp);
}
它先初始化root中的幾條鏈表.因為root中有一個top_cgroup.因此將root->number_of_cgroups置為1.然後,對root->top_cgroup進行初始化.使root->top_cgroup.root指向root. root->top_cgroup.top_cgroup指向它的本身.因為root->top_cgroup就是目錄下的第一個cgroup.
最後在init_cgroup_housekeeping()初始化cgroup的鏈表和讀寫鎖.
Cgroup_init_subsys()代碼如下:
static void __init cgroup_init_subsys(struct cgroup_subsys *ss)
{
struct cgroup_subsys_state *css;
printk(KERN_INFO "Initializing cgroup subsys %s\n", ss->name);
/* Create the top cgroup state for this subsystem */
ss->root = &rootnode;
css = ss->create(ss, dummytop);
/* We don't handle early failures gracefully */
BUG_ON(IS_ERR(css));
init_cgroup_css(css, ss, dummytop);
/* Update the init_css_set to contain a subsys
* pointer to this state - since the subsystem is
* newly registered, all tasks and hence the
* init_css_set is in the subsystem's top cgroup. */
init_css_set.subsys[ss->subsys_id] = dummytop->subsys[ss->subsys_id];
need_forkexit_callback |= ss->fork || ss->exit;
need_mm_owner_callback |= !!ss->mm_owner_changed;
/* At system boot, before all subsystems have been
* registered, no tasks have been forked, so we don't
* need to invoke fork callbacks here. */
BUG_ON(!list_empty(&init_task.tasks));
ss->active = 1;
}
dummytop定義如下:
#define dummytop (&rootnode.top_cgroup)
在這個函數中:
1):將每個要註冊的subsys->root都指向rootnode.
2):調用subsys->create()生成一個cgroup_subsys_state.
3):調用init_cgroup_css()將dummytop.subsys[i]設置成ss->create()生成的cgroup_subsys_state
4):更新init_css_set->subsys()對應項的值.
5):將ss->active設為1.表示它已經初始化了.
4.2: cgroup_init()
cgroup_init()是cgroup的第二階段的初始化.代碼如下:
int __init cgroup_init(void)
{
int err;
int i;
struct hlist_head *hhead;
err = bdi_init(&cgroup_backing_dev_info);
if (err)
return err;
//將剩下的(不需要在系統啟動時初始化的subsys)的subsys進行初始化
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
if (!ss->early_init)
cgroup_init_subsys(ss);
}
/* Add init_css_set to the hash table */
//將init_css_set添加到css_set_table[ ]
hhead = css_set_hash(init_css_set.subsys);
hlist_add_head(&init_css_set.hlist, hhead);
//註冊cgroup文件系統
err = register_filesystem(&cgroup_fs_type);
if (err < 0)
goto out;
//在proc文件系統的根目錄下創建一個名為cgroups的文件
proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations);
out:
if (err)
bdi_destroy(&cgroup_backing_dev_info);
return err;
}
這個函數比較簡單.首先.它將剩餘的subsys初始化.然後將init_css_set添加進哈希數組css_set_table[ ]中.在上面的代碼中css_set_hash()是css_set_table的哈希函數.它是css_set->subsys為哈希鍵值,到css_set_table[ ]中找到對應項.然後調用hlist_add_head()將init_css_set添加到衝突項中.
然後,註冊了cgroup文件系統.這個文件系統也是我們在用戶空間使用cgroup時必須掛載的.
最後,在proc的根目錄下創建了一個名為cgroups的文件.用來從用戶空間觀察cgroup的狀態.
經過cgroup的兩個階段的初始化, init_css_set, rootnode,subsys已經都初始化完成.表面上看起來它們很複雜,其實,它們只是表示cgroup的初始化狀態而已.例如,如果subsys->root等於rootnode,那表示subsys沒有被其它的cgroup所使用.
五:父子process之間的cgroup關聯
在上面看到的代碼中.將init_task.cgroup設置為了init_css_set.我們知道,init_task是系統的第一個process.所有的過程都是由它創建的.init_task.cgroup到底會在它後面的子process造成什麼樣的影響呢?接下來我們就來分析這個問題.
5.1:創建process時的父子processcgroup關聯
在process創建的時候,有:do_fork()àcopy_process(),有如下代碼片段:
static struct task_struct *copy_process(unsigned long clone_flags,
unsigned long stack_start,
struct pt_regs *regs,
unsigned long stack_size,
int __user *child_tidptr,
struct pid *pid,
int trace)
{
……
……
cgroup_fork(p);
……
cgroup_fork_callbacks(p);
……
cgroup_post_fork(p);
……
}
上面的代碼片段是創建新process的時候與cgroup關聯的函數.挨個分析如下:
void cgroup_fork(struct task_struct *child)
{
task_lock(current);
child->cgroups = current->cgroups;
get_css_set(child->cgroups);
task_unlock(current);
INIT_LIST_HEAD(&child->cg_list);
}
如上面代碼所示,子process和父process指向同一個cgroups.並且由於增加了一次引用.所以要調用get_css_set()來增加它的引用計數.最後初始化child->cg_list鏈表.
如代碼註釋上說的,這裡就有一個問題了:在dup_task_struct()為子process創建struct task_struct的時候不是已經複製了父process的cgroups麼?為什麼這裡還要對它進行一次賦值呢?這裡因為在dup_task_struct()中沒有持有保護鎖.而這裡又是一個競爭操作.因為在cgroup_attach_task()中可能會更改process的cgroups指向.因此通過cgroup_attach_task()所得到的cgroups可能是一個無效的指向.在遞增其引用計數的時候就會因為它是一個無效的引用而發生錯誤.所以,這個函數在加鎖的情況下進行操作.確保了父子process之間的同步.
cgroup_fork_callbacks()代碼如下,
void cgroup_fork_callbacks(struct task_struct *child)
{
if (need_forkexit_callback) {
int i;
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
if (ss->fork)
ss->fork(ss, child);
}
}
}
它主要是在process創建時調用subsys中的跟蹤函數:subsys->fork().
首先來跟蹤一下need_forkexita_callback這個變量.在如下代碼片段中:
static void __init cgroup_init_subsys(struct cgroup_subsys *ss)
{
……
need_forkexit_callback |= ss->fork || ss->exit;
……
}
從這段代碼中我們可以看到,如果有subsys定義了fork和exit函數,就會調need_forkexit_callback設置為1.
回到cgroup_fork_callback()這個函數中.我們發現.process會跟所有定義了fork的subsys進行這次操作.就算process沒有在這個subsys中,也會有這個操作.
Cgroup_pos_fork()如下所示:
void cgroup_post_fork(struct task_struct *child)
{
if (use_task_css_set_links) {
write_lock(&css_set_lock);
if (list_empty(&child->cg_list))
list_add(&child->cg_list, &child->cgroups->tasks);
write_unlock(&css_set_lock);
}
在use_task_css_set_link為1的情況下.就將子process鏈入到它所指向的css_set->task鏈表.
那什麼時候會將use_task_css_set_link設置為1呢?實際上,當你往cgroup中添加process的時候就會將其置1了.
例如我們之前舉的一個例子中:
echo $$ > /dev/cgroup/eric_task/tasks
這個過程就會將use_task_css_set_link置1了.這個過程我們之後再來詳細分析.
5.2:子process結束時的操作
子process結束的時候,有:
Do_exit() à cgroup_exit().
Cgroup_exit()代碼如下:
void cgroup_exit(struct task_struct *tsk, int run_callbacks)
{
int i;
struct css_set *cg;
if (run_callbacks && need_forkexit_callback) {
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
if (ss->exit)
ss->exit(ss, tsk);
}
}
/*
* Unlink from the css_set task list if necessary.
* Optimistically check cg_list before taking
* css_set_lock
*/
if (!list_empty(&tsk->cg_list)) {
write_lock(&css_set_lock);
if (!list_empty(&tsk->cg_list))
list_del(&tsk->cg_list);
write_unlock(&css_set_lock);
}
/* Reassign the task to the init_css_set. */
task_lock(tsk);
cg = tsk->cgroups;
tsk->cgroups = &init_css_set;
task_unlock(tsk);
if (cg)
put_css_set_taskexit(cg);
}
這個函數的代碼邏輯比較清晰.首先,如果以1為調用參數(run_callbacks為1),且有定義了exit操作的subsys.就調用這個subsys的exit操作.
然後斷開task->cg_list鏈表.將其從所指向的css_set->task鏈上斷開.
最後,斷開當前的cgroup指向.將其指向init_css_set.也就是將其回覆到初始狀態.最後,減少舊指向css_set的引用計數.
在這個函數中,我們來跟蹤分析put_css_set_taskexit(),代碼如下:
static inline void put_css_set_taskexit(struct css_set *cg)
{
__put_css_set(cg, 1);
}
跟蹤到__put_css_set()中:
static void __put_css_set(struct css_set *cg, int taskexit)
{
int i;
/*
* Ensure that the refcount doesn't hit zero while any readers
* can see it. Similar to atomic_dec_and_lock(), but for an
* rwlock
*/
if (atomic_add_unless(&cg->refcount, -1, 1))
return;
write_lock(&css_set_lock);
if (!atomic_dec_and_test(&cg->refcount)) {
write_unlock(&css_set_lock);
return;
}
unlink_css_set(cg);
write_unlock(&css_set_lock);
rcu_read_lock();
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup *cgrp = cg->subsys[i]->cgroup;
if (atomic_dec_and_test(&cgrp->count) &&
notify_on_release(cgrp)) {
if (taskexit)
set_bit(CGRP_RELEASABLE, &cgrp->flags);
check_for_release(cgrp);
}
}
rcu_read_unlock();
kfree(cg);
}
atomic_add_unless(v,a,u)表示如果v的值不為u就加a.返回1.如果v的值等於u就返回0
因此,這個函數首先減小css_set的引用計數.如果css_set的引用計數為1.就會將css_set釋放掉了. 要釋放css_set.首先要釋放css_set上掛載的鏈表.再釋放css_set結構本身所佔空間.
釋放css_set上的掛載鏈表是在unlink_css_set()中完成的.代碼如下:
static void unlink_css_set(struct css_set *cg)
{
struct cg_cgroup_link *link;
struct cg_cgroup_link *saved_link;
hlist_del(&cg->hlist);
css_set_count--;
list_for_each_entry_safe(link, saved_link, &cg->cg_links,
cg_link_list) {
list_del(&link->cg_link_list);
list_del(&link->cgrp_link_list);
kfree(link);
}
}
它首先將cg->hlist斷開,也就是將其從css_set_table[ ]中刪除.然後減小css_set_count計數.最後遍歷刪除與css_set關聯的cg_cgroup_link.
另外,在這個函數中還涉及到了notify_on_release的操作.在後面再來詳細分析這一過程.這裡先把它放一下.
六:cgroup文件系統的掛載
Cgroup文件系統定義如下:
static struct file_system_type cgroup_fs_type = {
.name = "cgroup",
.get_sb = cgroup_get_sb,
.kill_sb = cgroup_kill_sb,
}
根據我們之前有關linux文件系統系列的文析.在掛載文件系統的時候,流程會流入file_system_type.get_sb().也就是cgroup_get_sb().由於該代碼較長.分段分析如下:
static int cgroup_get_sb(struct file_system_type *fs_type,
int flags, const char *unused_dev_name,
void *data, struct vfsmount *mnt)
{
struct cgroup_sb_opts opts;
int ret = 0;
struct super_block *sb;
struct cgroupfs_root *root;
struct list_head tmp_cg_links;
/* First find the desired set of subsystems */
//解析掛載參數
ret = parse_cgroupfs_options(data, &opts);
if (ret) {
if (opts.release_agent)
kfree(opts.release_agent);
return ret;
}
在這一部份,解析掛載的參數,並將解析的結果存放到opts.opts-> subsys_bits表示指定關聯的subsys位圖,opts->flags:掛載的標誌: opts->release_agent表示指定的release_agent路徑.
//分配並初始化cgroufs_root
root = kzalloc(sizeof(*root), GFP_KERNEL);
if (!root) {
if (opts.release_agent)
kfree(opts.release_agent);
return -ENOMEM;
}
init_cgroup_root(root);
/*root->subsys_bits: 該hierarchy上關聯的subsys*/
root->subsys_bits = opts.subsys_bits;
root->flags = opts.flags;
/*如果帶了release_agent參數,將其copy到root0<release_agent_path*/
if (opts.release_agent) {
strcpy(root->release_agent_path, opts.release_agent);
kfree(opts.release_agent);
}
/*初始化一個super block*/
sb = sget(fs_type, cgroup_test_super, cgroup_set_super, root);
/*如果發生錯誤*/
if (IS_ERR(sb)) {
kfree(root);
return PTR_ERR(sb);
}
在這一部份,主要分配並初始化了一個cgroupfs_root結構.裡面的子函數init_cgroup_root()我們在之前已經分析過,這裡不再贅述.其實的初始化包括:設置與之關聯的subsys位圖,掛載標誌和release_agent路徑.然後再調用sget()生成一個super_block結構.調用cgroup_test_super來判斷系統中是否有機同的cgroups_root.調用cgroup_set_super來對super_block進行初始化.
在cgroup_set_super()中,將sb->s_fs_info 指向了cgroutfs_root,cgroufs_root.sb指向生成的super_block.
類似的.如果找到的super_block相關聯的cgroupfs_root所表示的subsys_bits和flags與當前cgroupfs_root相同的話,就表示是一個相同的super_block.因為它們的掛載參數是一樣的.
舉個例子來說明一下有重複super_block的情況:
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/eric_cgroup/
在上面的例子中,在掛載到/dev/eric_cgroup目錄的時候,就會找到一個相同的super_block.這樣實例上兩者的操作是一樣的.這兩個不同掛載點所代碼的vfsmount會找到同一個super_block.也就是說對其中一個目錄的操作都會同表現在另一個目錄中.
/*重複掛載*/
if (sb->s_fs_info != root) {
/* Reusing an existing superblock */
BUG_ON(sb->s_root == NULL);
kfree(root);
root = NULL;
} else {
/* New superblock */
struct cgroup *cgrp = &root->top_cgroup;
struct inode *inode;
int i;
BUG_ON(sb->s_root != NULL);
/*初始化super_block對應的dentry和inode*/
ret = cgroup_get_rootdir(sb);
if (ret)
goto drop_new_super;
inode = sb->s_root->d_inode;
mutex_lock(&inode->i_mutex);
mutex_lock(&cgroup_mutex);
/*
* We're accessing css_set_count without locking
* css_set_lock here, but that's OK - it can only be
* increased by someone holding cgroup_lock, and
* that's us. The worst that can happen is that we
* have some link structures left over
*/
/*分配css_set_count個cg_cgroup_link並將它們鏈入到tmp_cg_links*/
ret = allocate_cg_links(css_set_count, &tmp_cg_links);
if (ret) {
mutex_unlock(&cgroup_mutex);
mutex_unlock(&inode->i_mutex);
goto drop_new_super;
}
/*bind subsys 到hierarchy*/
ret = rebind_subsystems(root, root->subsys_bits);
if (ret == -EBUSY) {
mutex_unlock(&cgroup_mutex);
mutex_unlock(&inode->i_mutex);
goto drop_new_super;
}
/* EBUSY should be the only error here */
BUG_ON(ret);
/*將root添加到roots鏈入.增加root_count計數*/
list_add(&root->root_list, &roots);
root_count++;
/*將掛載根目錄dentry的私有結構d_fsdata反映向root->top_cgroup*/
/*將root->top_cgroup.dentry指向掛載的根目錄*/
sb->s_root->d_fsdata = &root->top_cgroup;
root->top_cgroup.dentry = sb->s_root;
/* Link the top cgroup in this hierarchy into all
* the css_set objects */
/*將所有的css_set都和root->top_cgroup關聯起來*/
write_lock(&css_set_lock);
for (i = 0; i < CSS_SET_TABLE_SIZE; i++) {
struct hlist_head *hhead = &css_set_table[i];
struct hlist_node *node;
struct css_set *cg;
hlist_for_each_entry(cg, node, hhead, hlist) {
struct cg_cgroup_link *link;
BUG_ON(list_empty(&tmp_cg_links));
link = list_entry(tmp_cg_links.next,
struct cg_cgroup_link,
cgrp_link_list);
list_del(&link->cgrp_link_list);
link->cg = cg;
list_add(&link->cgrp_link_list,
&root->top_cgroup.css_sets);
list_add(&link->cg_link_list, &cg->cg_links);
}
}
write_unlock(&css_set_lock);
/*釋放tmp_cg_links的多餘項*/
free_cg_links(&tmp_cg_links);
BUG_ON(!list_empty(&cgrp->sibling));
BUG_ON(!list_empty(&cgrp->children));
BUG_ON(root->number_of_cgroups != 1);
/*在root->top_cgroup下面創建一些文件,包括cgroup共有的和subsys私有的文件*/
cgroup_populate_dir(cgrp);
mutex_unlock(&inode->i_mutex);
mutex_unlock(&cgroup_mutex);
}
/*將vfsmount和super_block關聯起來*/
return simple_set_mnt(mnt, sb);
drop_new_super:
up_write(&sb->s_umount);
deactivate_super(sb);
free_cg_links(&tmp_cg_links);
return ret;
}
這一部份,首先判斷找到的super_block是不是之前就存在的.如果是已經存在的,那就用不著再初始化一個cgroupfs_root結構了.將之前分配的結構釋放掉.然後調用simple_set_mnt()將取得的super_block和vfsmount相關聯後退出.
如果super_block是一個新建的.那麼就必須要繼續初始化cgroupfs_root了.
首先,調用cgroup_get_rootdir()初始化super_block對應的dentry和inode.
然後,調用rebind_subsystems()將需要關聯到hierarchy的subsys和root->top_cgroup綁定起來.
最後,將所有的css_set都和root->top_cgroup關聯起來.這樣就可以從root->top_cgroup找到所有的process了.再調用cgroup_populate_dir()在掛載目錄下創建一些文件,然後,調用simple_set_mnt()將取得的super_block和vfsmount相關聯後退出.
這個函數的流程還算簡單.下面來分析一下里面涉及到的重要的子函數:
6.1: parse_cgroupfs_options()函數分析
這個函數主要是對掛載的參數進行解析.函數代碼如下:
static int parse_cgroupfs_options(char *data,
struct cgroup_sb_opts *opts)
{
/*如果掛載的時候沒有帶參數,將o設為"all".表示將所有
*的subsys都與之關聯
*/
char *token, *o = data ?: "all";
opts->subsys_bits = 0;
opts->flags = 0;
opts->release_agent = NULL;
/*各參數是以","分隔的*/
while ((token = strsep(&o, ",")) != NULL) {
if (!*token)
return -EINVAL;
/*如果為all.表示關聯所有的subsys*/
if (!strcmp(token, "all")) {
/* Add all non-disabled subsystems */
int i;
opts->subsys_bits = 0;
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
if (!ss->disabled)
opts->subsys_bits |= 1ul << i;
}
}
/*如果指定參數noprefix.設定ROOT_NOPREFIX標誌*/
/*在指定noprefix的情況下.subsys創建的文件不會帶subsys名稱的前綴*/
else if (!strcmp(token, "noprefix")) {
set_bit(ROOT_NOPREFIX, &opts->flags);
}
/*如果指定了release_agent.分opt->release_agent分配內存,並將參數copy到裡面*/
else if (!strncmp(token, "release_agent=", 14)) {
/* Specifying two release agents is forbidden */
if (opts->release_agent)
return -EINVAL;
opts->release_agent = kzalloc(PATH_MAX, GFP_KERNEL);
if (!opts->release_agent)
return -ENOMEM;
strncpy(opts->release_agent, token + 14, PATH_MAX - 1);
opts->release_agent[PATH_MAX - 1] = 0;
}
/*其它情況下,將所帶參數做為一個susys名處理.到sussys[]找到
*對應的subsys.然後將opts->subsys_bits中的位置1
*/
else {
struct cgroup_subsys *ss;
int i;
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
ss = subsys[i];
if (!strcmp(token, ss->name)) {
if (!ss->disabled)
set_bit(i, &opts->subsys_bits);
break;
}
}
if (i == CGROUP_SUBSYS_COUNT)
return -ENOENT;
}
}
/* We can't have an empty hierarchy */
/*如果沒有關聯到subsys.錯誤*/
if (!opts->subsys_bits)
return -EINVAL;
return 0;
}
對照代碼中添加的註釋應該很容易看懂.這裡就不再做詳細分析了.
6.2: rebind_subsystems()函數分析
rebind_subsystems()用來將cgroupfs_root和subsys綁定.代碼如下:
static int rebind_subsystems(struct cgroupfs_root *root,
unsigned long final_bits)
{
unsigned long added_bits, removed_bits;
struct cgroup *cgrp = &root->top_cgroup;
int i;
/*root->actual_subsys_bits表示當進root中所關鍵的subsys位圖*/
/*如果在root->actual_subsys_bits中.但沒有在final_bits中.表示這是
*一次remonut的操作.需要將舊的subsys移除.如果在final_bits中
*存在,但沒有在root->actual_subsys_bits中,表示是需要添加的.
*/
removed_bits = root->actual_subsys_bits & ~final_bits;
added_bits = final_bits & ~root->actual_subsys_bits;
/* Check that any added subsystems are currently free */
/*如果要關聯的subsys已經在其它的hierarchy中了.失敗.
*如果ss->root != &rootnode表示ss已經鏈入了其它的cgroupfs_root
*/
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
unsigned long bit = 1UL << i;
struct cgroup_subsys *ss = subsys[i];
if (!(bit & added_bits))
continue;
if (ss->root != &rootnode) {
/* Subsystem isn't free */
return -EBUSY;
}
}
/* Currently we don't handle adding/removing subsystems when
* any child cgroups exist. This is theoretically supportable
* but involves complex error handling, so it's being left until
* later */
/*如果root->top_cgroup->children不為空.表示該hierarchy還要其它的cgroup
*是不能被remount的.(新掛載的root->top_cgroup在初始化的時候將children置空了)
*/
if (!list_empty(&cgrp->children))
return -EBUSY;
/* Process each subsystem */
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
unsigned long bit = 1UL << i;
/*添加subsys的情況*/
if (bit & added_bits) {
/* We're binding this subsystem to this hierarchy */
/* 添加情況下.將cgrp->subsys[i]指向dummytop->subsys[i]
* 並更新dummytop->subsys[i]->root.將其指向要添加的root
* 最後調用subsys->bind()操作
*/
BUG_ON(cgrp->subsys[i]);
BUG_ON(!dummytop->subsys[i]);
BUG_ON(dummytop->subsys[i]->cgroup != dummytop);
cgrp->subsys[i] = dummytop->subsys[i];
cgrp->subsys[i]->cgroup = cgrp;
list_add(&ss->sibling, &root->subsys_list);
rcu_assign_pointer(ss->root, root);
if (ss->bind)
ss->bind(ss, cgrp);
}
/*移除subsys的情況*/
else if (bit & removed_bits) {
/* 移除操作,將對應的cgroup_subsys_state回歸到原來的樣子.並且也需要
* 將與其subsys bind
*/
/* We're removing this subsystem */
BUG_ON(cgrp->subsys[i] != dummytop->subsys[i]);
BUG_ON(cgrp->subsys[i]->cgroup != cgrp);
if (ss->bind)
ss->bind(ss, dummytop);
dummytop->subsys[i]->cgroup = dummytop;
cgrp->subsys[i] = NULL;
rcu_assign_pointer(subsys[i]->root, &rootnode);
list_del(&ss->sibling);
} else if (bit & final_bits) {
/* Subsystem state should already exist */
BUG_ON(!cgrp->subsys[i]);
} else {
/* Subsystem state shouldn't exist */
BUG_ON(cgrp->subsys[i]);
}
}
/*更新root的位圖*/
root->subsys_bits = root->actual_subsys_bits = final_bits;
synchronize_rcu();
return 0;
}
從這個函數也可以看出來.rootnode就是起一個參照的作用.用來判斷subsys是否處於初始化狀態.
6.3: cgroup_populate_dir()函數分析
cgroup_populate_dir()用來在掛載目錄下創建交互文件.代碼如下:
static int cgroup_populate_dir(struct cgroup *cgrp)
{
int err;
struct cgroup_subsys *ss;
/* First clear out any existing files */
/*先將cgrp所在的目錄清空*/
cgroup_clear_directory(cgrp->dentry);
/*創建files所代碼的幾個文件*/
err = cgroup_add_files(cgrp, NULL, files, ARRAY_SIZE(files));
if (err < 0)
return err;
/*如果是頂層top_cgroup.創建cft_release_agent所代碼的文件*/
if (cgrp == cgrp->top_cgroup) {
if ((err = cgroup_add_file(cgrp, NULL, &cft_release_agent)) < 0)
return err;
}
/*對所有與cgrp->root關聯的subsys都調用populate()*/
for_each_subsys(cgrp->root, ss) {
if (ss->populate && (err = ss->populate(ss, cgrp)) < 0)
return err;
}
return 0;
}
這個函數比較簡單.跟蹤cgroup_add_file().如下:
nt cgroup_add_file(struct cgroup *cgrp,
struct cgroup_subsys *subsys,
const struct cftype *cft)
{
struct dentry *dir = cgrp->dentry;
struct dentry *dentry;
int error;
char name[MAX_CGROUP_TYPE_NAMELEN + MAX_CFTYPE_NAME + 2] = { 0 };
/*如果有指定subsys.且沒有使用ROOT_NOPREFIX標誌.需要在名稱前加上
*subsys的名稱
*/
if (subsys && !test_bit(ROOT_NOPREFIX, &cgrp->root->flags)) {
strcpy(name, subsys->name);
strcat(name, ".");
}
/*將cft->name鏈接到name代表的字串後面*/
strcat(name, cft->name);
BUG_ON(!mutex_is_locked(&dir->d_inode->i_mutex));
/*到cgroup所在的目錄下尋找name所表示的dentry,如果不存在,則新建之*/
dentry = lookup_one_len(name, dir, strlen(name));
if (!IS_ERR(dentry)) {
/*創建文件inode*/
error = cgroup_create_file(dentry, 0644 | S_IFREG,
cgrp->root->sb);
/*使dentry->d_fsdata指向文件所代表的cftype*/
if (!error)
dentry->d_fsdata = (void *)cft;
dput(dentry);
} else
error = PTR_ERR(dentry);
return error;
}
cgroup_create_file()函數代碼如下:
static int cgroup_create_file(struct dentry *dentry, int mode,
struct super_block *sb)
{
static struct dentry_operations cgroup_dops = {
.d_iput = cgroup_diput,
};
struct inode *inode;
if (!dentry)
return -ENOENT;
if (dentry->d_inode)
return -EEXIST;
/*分配一個inode*/
inode = cgroup_new_inode(mode, sb);
if (!inode)
return -ENOMEM;
/*如果新建的是目錄*/
if (S_ISDIR(mode)) {
inode->i_op = &cgroup_dir_inode_operations;
inode->i_fop = &simple_dir_operations;
/* start off with i_nlink == 2 (for "." entry) */
inc_nlink(inode);
/* start with the directory inode held, so that we can
* populate it without racing with another mkdir */
mutex_lock_nested(&inode->i_mutex, I_MUTEX_CHILD);
}
/*新建一般文件*/
else if (S_ISREG(mode)) {
inode->i_size = 0;
inode->i_fop = &cgroup_file_operations;
}
dentry->d_op = &cgroup_dops;
/*將dentry和inode關聯起來*/
d_instantiate(dentry, inode);
dget(dentry); /* Extra count - pin the dentry in core */
return 0;
}
從這個函數我們可以看到.如果是目錄的話,對應的操作集為simple_dir_operations和cgroup_dir_inode_operations.它與cgroup_get_rootdir()中對根目錄對應的inode所設置的操作集是一樣的.如果是一般文件,它的操作集為cgroup_file_operations.
在這裡,先將cgroup中的文件操作放到一邊,我們在之後再來詳細分析這個過程.
現在.我們已經將cgroup文件系統的掛載分析完成.接下來看它下面子層cgroup的創建.
七:創建子層cgroup
在目錄下通過mkdir調用就可以創建一個子層cgroup.下面就分析這一過程:
經過上面的分析可以得知,cgroup中目錄的操作集為: cgroup_dir_inode_operations.結構如下:
static struct inode_operations cgroup_dir_inode_operations = {
.lookup = simple_lookup,
.mkdir = cgroup_mkdir,
.rmdir = cgroup_rmdir,
.rename = cgroup_rename,
};
從上面看到,對應mkdir的入口為cgroup_mkdir().代碼如下:
static int cgroup_mkdir(struct inode *dir, struct dentry *dentry, int mode)
{
/*找到它的上一級cgroup*/
struct cgroup *c_parent = dentry->d_parent->d_fsdata;
/* the vfs holds inode->i_mutex already */
/*調用cgroup_create創建cgroup*/
return cgroup_create(c_parent, dentry, mode | S_IFDIR);
}
跟蹤cgroup_create().代碼如下:
static long cgroup_create(struct cgroup *parent, struct dentry *dentry,
int mode)
{
struct cgroup *cgrp;
struct cgroupfs_root *root = parent->root;
int err = 0;
struct cgroup_subsys *ss;
struct super_block *sb = root->sb;
/*分配並初始化一個cgroup*/
cgrp = kzalloc(sizeof(*cgrp), GFP_KERNEL);
if (!cgrp)
return -ENOMEM;
/* Grab a reference on the superblock so the hierarchy doesn't
* get deleted on unmount if there are child cgroups. This
* can be done outside cgroup_mutex, since the sb can't
* disappear while someone has an open control file on the
* fs */
atomic_inc(&sb->s_active);
mutex_lock(&cgroup_mutex);
init_cgroup_housekeeping(cgrp);
/*設置cgrp的層次關係*/
cgrp->parent = parent;
cgrp->root = parent->root;
cgrp->top_cgroup = parent->top_cgroup;
/*如果上一級cgroup設置了CGRP_NOTIFY_ON_RELEASE.那cgrp也設置這個標誌*/
if (notify_on_release(parent))
set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
/*調用subsys_create()生成cgroup_subsys_state.並與cgrp相關聯*/
for_each_subsys(root, ss) {
struct cgroup_subsys_state *css = ss->create(ss, cgrp);
if (IS_ERR(css)) {
err = PTR_ERR(css);
goto err_destroy;
}
init_cgroup_css(css, ss, cgrp);
}
/*將cgrp添加到上一層cgroup的children鏈表*/
list_add(&cgrp->sibling, &cgrp->parent->children);
/*增加root的cgroups數目計數*/
root->number_of_cgroups++;
/*在當前目錄生成一個目錄*/
err = cgroup_create_dir(cgrp, dentry, mode);
if (err < 0)
goto err_remove;
/* The cgroup directory was pre-locked for us */
BUG_ON(!mutex_is_locked(&cgrp->dentry->d_inode->i_mutex));
/*在cgrp下創建幾個交互文件*/
err = cgroup_populate_dir(cgrp);
/* If err < 0, we have a half-filled directory - oh well ;) */
mutex_unlock(&cgroup_mutex);
mutex_unlock(&cgrp->dentry->d_inode->i_mutex);
return 0;
err_remove:
list_del(&cgrp->sibling);
root->number_of_cgroups--;
err_destroy:
for_each_subsys(root, ss) {
if (cgrp->subsys[ss->subsys_id])
ss->destroy(ss, cgrp);
}
mutex_unlock(&cgroup_mutex);
/* Release the reference count that we took on the superblock */
deactivate_super(sb);
kfree(cgrp);
return err;
}
在這個函數中,主要分配並初始化了一個cgroup結構.並且將它和它的上一層目錄以及整個cgroupfs_root構成一個空間層次關係.然後,再調用subsys>create()操作函數.來讓subsys知道已經創建了一個cgroup結構.
為了理順這一部份.將前面分析的cgroup文件系統掛載和cgroup的創建.以及接下來要分析的attach_task()操作總結成一個圖.如下示:
八:cgroup中文件的操作
接下來,就來看cgroup文件的操作.在上面曾分析到:文件對應的操作集為cgroup_file_operations.如下所示:
static struct file_operations cgroup_file_operations = {
.read = cgroup_file_read,
.write = cgroup_file_write,
.llseek = generic_file_llseek,
.open = cgroup_file_open,
.release = cgroup_file_release,
}
7.1:cgrou文件的open操作
對應的函數為cgroup_file_open().代碼如下:
static int cgroup_file_open(struct inode *inode, struct file *file)
{
int err;
struct cftype *cft;
err = generic_file_open(inode, file);
if (err)
return err;
/*取得文件對應的struct cftype*/
cft = __d_cft(file->f_dentry);
if (!cft)
return -ENODEV;
/*如果定義了read_map或者是read_seq_string*/
if (cft->read_map || cft->read_seq_string) {
struct cgroup_seqfile_state *state =
kzalloc(sizeof(*state), GFP_USER);
if (!state)
return -ENOMEM;
state->cft = cft;
state->cgroup = __d_cgrp(file->f_dentry->d_parent);
file->f_op = &cgroup_seqfile_operations;
err = single_open(file, cgroup_seqfile_show, state);
if (err < 0)
kfree(state);
}
/*否則調用cft->open()*/
else if (cft->open)
err = cft->open(inode, file);
else
err = 0;
return err;
}
有兩種情況.一種是定義了read_map或者是read_seq_string的情況.這種情況下,它對應的操作集為cgroup_seqfile_operations.如果是其它的情況.調用cftype的open()函數.第一種情況,我們等以後遇到了這樣的情況再來詳細分析.
7.2:cgroup文件的read操作
對應函數為cgroup_file_read().代碼如下:
static ssize_t cgroup_file_read(struct file *file, char __user *buf,
size_t nbytes, loff_t *ppos)
{
struct cftype *cft = __d_cft(file->f_dentry);
struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
if (!cft || cgroup_is_removed(cgrp))
return -ENODEV;
if (cft->read)
return cft->read(cgrp, cft, file, buf, nbytes, ppos);
if (cft->read_u64)
return cgroup_read_u64(cgrp, cft, file, buf, nbytes, ppos);
if (cft->read_s64)
return cgroup_read_s64(cgrp, cft, file, buf, nbytes, ppos);
return -EINVAL;
}
如上代碼所示.read操作會轉入到cftype的read()或者read_u64或者read_s64的函數中.
7.3:cgroup文件的wirte操作
對應的操作函數是cgroup_file_write().如下示:
static ssize_t cgroup_file_write(struct file *file, const char __user *buf,
size_t nbytes, loff_t *ppos)
{
struct cftype *cft = __d_cft(file->f_dentry);
struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
if (!cft || cgroup_is_removed(cgrp))
return -ENODEV;
if (cft->write)
return cft->write(cgrp, cft, file, buf, nbytes, ppos);
if (cft->write_u64 || cft->write_s64)
return cgroup_write_X64(cgrp, cft, file, buf, nbytes, ppos);
if (cft->write_string)
return cgroup_write_string(cgrp, cft, file, buf, nbytes, ppos);
if (cft->trigger) {
int ret = cft->trigger(cgrp, (unsigned int)cft->private);
return ret ? ret : nbytes;
}
return -EINVAL;
}
從上面可以看到.最終的操作會轉入到cftype的write或者wirte_u64或者wirte_string或者trigger函數中.
7.4:debug subsytem分析
以debug subsystem為例來說明cgroup中的文件操作
Debug subsys定義如下:
struct cgroup_subsys debug_subsys = {
.name = "debug",
.create = debug_create,
.destroy = debug_destroy,
.populate = debug_populate,
.subsys_id = debug_subsys_id,
}
在cgroup_init_subsys()中,會以dummytop為參數調用debug.create().對應函數為debug_create().代碼如下:
static struct cgroup_subsys_state *debug_create(struct cgroup_subsys *ss,
struct cgroup *cont)
{
struct cgroup_subsys_state *css = kzalloc(sizeof(*css), GFP_KERNEL);
if (!css)
return ERR_PTR(-ENOMEM);
return css;
}
這裡沒啥好說的,就是分配了一個cgroup_subsys_state結構.
然後,將cgroup掛載.指令如下:
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/
在rebind_subsystems()中,會調用subsys的bind函數.但在debug中無此接口.故不需要考慮.
然後在cgroup_populate_dir()中會調用populate接口.對應函數為debug_populate().代碼如下:
static int debug_populate(struct cgroup_subsys *ss, struct cgroup *cont)
{
return cgroup_add_files(cont, ss, files, ARRAY_SIZE(files));
}
Debug中的files定義如下:
static struct cftype files[] = {
{
.name = "cgroup_refcount",
.read_u64 = cgroup_refcount_read,
},
{
.name = "taskcount",
.read_u64 = taskcount_read,
},
{
.name = "current_css_set",
.read_u64 = current_css_set_read,
},
{
.name = "current_css_set_refcount",
.read_u64 = current_css_set_refcount_read,
},
{
.name = "releasable",
.read_u64 = releasable_read,
},
}
來觀察一下 /dev/cgroup下的文件:
[root@localhost ~]# tree /dev/cgroup/
/dev/cgroup/
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- notify_on_release
|-- release_agent
`-- tasks
0 directories, 8 files
上面帶debug字樣的文件是從debug subsys中創建的.其它的是cgroup.c的files中創建的.
我們先來分析每一個subsys共有的文件.即tasks,release_agent和notify_on_release.
7.5:task文件操作
Tasks文件對應的cftype結構如下:
static struct cftype files[] = {
{
.name = "tasks",
.open = cgroup_tasks_open,
.write_u64 = cgroup_tasks_write,
.release = cgroup_tasks_release,
.private = FILE_TASKLIST,
}
7.5.1:task文件的open操作
當打開文件時,流程就會轉入cgroup_tasks_open().代碼如下:
static int cgroup_tasks_open(struct inode *unused, struct file *file)
{
/*取得該文件所在層次的cgroup*/
struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
pid_t *pidarray;
int npids;
int retval;
/* Nothing to do for write-only files */
/*如果是只寫的文件系統*/
if (!(file->f_mode & FMODE_READ))
return 0;
/*
* If cgroup gets more users after we read count, we won't have
* enough space - tough. This race is indistinguishable to the
* caller from the case that the additional cgroup users didn't
* show up until sometime later on.
*/
/*得到該層cgroup所關聯的process個數*/
npids = cgroup_task_count(cgrp);
/*為npids個process的pid存放分配空間*/
pidarray = kmalloc(npids * sizeof(pid_t), GFP_KERNEL);
if (!pidarray)
return -ENOMEM;
/* 將與cgroup關聯process的pid存放到pid_array_load數組.
* 並且按照從小到大的順序排列
*/
npids = pid_array_load(pidarray, npids, cgrp);
sort(pidarray, npids, sizeof(pid_t), cmppid, NULL);
/*
* Store the array in the cgroup, freeing the old
* array if necessary
*/
/* 將npids,pidarray信息存放到cgroup中.如果cgroup之前
* 就有task_pids.將其佔放的空間釋放
*/
down_write(&cgrp->pids_mutex);
kfree(cgrp->tasks_pids);
cgrp->tasks_pids = pidarray;
cgrp->pids_length = npids;
cgrp->pids_use_count++;
up_write(&cgrp->pids_mutex);
/*將文件對應的操作集更改為cgroup_task_operations*/
file->f_op = &cgroup_tasks_operations;
retval = seq_open(file, &cgroup_tasks_seq_operations);
/*如果操作失敗,將cgroup中的pid信息釋放*/
if (retval) {
release_cgroup_pid_array(cgrp);
return retval;
}
((struct seq_file *)file->private_data)->private = cgrp;
return 0;
}
首先,我們來思考一下這個問題:怎麼得到與cgroup關聯的process呢?
回到在上面列出來的資料結構關係圖.每個process都會指向一個css_set.而與這個css_set關聯的所有process都會鏈入到css_set->tasks鏈表.而cgroup又可能通過一個中間結構cg_cgroup_link來尋找所有與之關聯的所有css_set.從而可以得到與cgroup關聯的所有process.
在上面的代碼中,通過調用cgroup_task_count()來得到與之關聯的process數目,代碼如下:
int cgroup_task_count(const struct cgroup *cgrp)
{
int count = 0;
struct cg_cgroup_link *link;
read_lock(&css_set_lock);
list_for_each_entry(link, &cgrp->css_sets, cgrp_link_list) {
count += atomic_read(&link->cg->refcount);
}
read_unlock(&css_set_lock);
return count;
}
它就是遍歷cgro->css_sets.並調其轉換為cg_cgroup_link.再從這個link得到css_set.這個css_set的引用計數就是與這個指向這個css_set的task數目.
在代碼中,是通過pid_array_load()來得到與cgroup關聯的task,並且將process的pid寫入數組pidarray中.代碼如下:
static int pid_array_load(pid_t *pidarray, int npids, struct cgroup *cgrp)
{
int n = 0;
struct cgroup_iter it;
struct task_struct *tsk;
cgroup_iter_start(cgrp, &it);
while ((tsk = cgroup_iter_next(cgrp, &it))) {
if (unlikely(n == npids))
break;
pidarray[n++] = task_pid_vnr(tsk);
}
cgroup_iter_end(cgrp, &it);
return n;
}
我們在這裡遇到了一個新的結構:struct cgroup_iter.它是cgroup的一個迭代器,通過它可以遍歷取得與cgroup關聯的task.它的使用方法為:
1:調用cgroup_iter_start()來初始化這個迭代碼.
2:調用cgroup_iter_next()用來取得cgroup中的下一個task
3:使用完了,調用cgroup_iner_end().
下面來分析這三個過程:
Cgroup_iter_start()代碼如下:
void cgroup_iter_start(struct cgroup *cgrp, struct cgroup_iter *it)
{
/*
* The first time anyone tries to iterate across a cgroup,
* we need to enable the list linking each css_set to its
* tasks, and fix up all existing tasks.
*/
if (!use_task_css_set_links)
cgroup_enable_task_cg_lists();
read_lock(&css_set_lock);
it->cg_link = &cgrp->css_sets;
cgroup_advance_iter(cgrp, it);
}
我們在這裡再次遇到了use_task_css_set_links變量.在之前分析cgroup_post_fork()中的時候,我們曾說過,只有在use_task_css_set_link設置為1的時候,才會調task->cg_list鏈入到css_set->tasks中.
所以,在這個地方,如果use_task_css_set_link為0.那就必須要將之前所有的process都鏈入到它所指向的css_set->tasks鏈表.這個過程是在cgroup_enable_task_cg_lists()完成的,這個函數相當簡單,就是一個task的遍歷,然後就是鏈表的鏈入,在這裡就不再詳細分析了.請自行閱讀它的代碼.*^_^*
然後,將it->cg_link指向cgrp->css_sets.我們在前面說過,可以通過cgrp->css_sets就可以得得所有的與cgroup關聯的css_set.
到這裡,這個迭代器裡面還是空的,接下來往裡面填充資料.這個過程是在cgroup_advance_iter()中完成,代碼如下示:
static void cgroup_advance_iter(struct cgroup *cgrp,
struct cgroup_iter *it)
{
struct list_head *l = it->cg_link;
struct cg_cgroup_link *link;
struct css_set *cg;
/* Advance to the next non-empty css_set */
do {
l = l->next;
if (l == &cgrp->css_sets) {
it->cg_link = NULL;
return;
}
link = list_entry(l, struct cg_cgroup_link, cgrp_link_list);
cg = link->cg;
} while (list_empty(&cg->tasks));
it->cg_link = l;
it->task = cg->tasks.next;
}
通過前面的分析可得知,可通過it->cg_link找到與之關聯的css_set,然後再通過css_set找到與它關聯的task鏈表.因此每次往cgroup迭代器裡填充資料,就是找到一個tasks鏈表不為空的css_set.取資料就從css_set->tasks中取.如果資料取完了,就找下一個tasks鏈表不為空的css_set.
這樣,這個函數的代碼就很簡單了.它就是找到it->cg_link上tasks鏈表不為空的css_set項.
cgroup_iter_next()的代碼如下:
struct task_struct *cgroup_iter_next(struct cgroup *cgrp,
struct cgroup_iter *it)
{
struct task_struct *res;
struct list_head *l = it->task;
/* If the iterator cg is NULL, we have no tasks */
if (!it->cg_link)
return NULL;
res = list_entry(l, struct task_struct, cg_list);
/* Advance iterator to find next entry */
l = l->next;
if (l == &res->cgroups->tasks) {
/* We reached the end of this task list - move on to
* the next cg_cgroup_link */
cgroup_advance_iter(cgrp, it);
} else {
it->task = l;
}
return res;
}
如果it->cg_link為空表示it->cg_link已經遍歷完了,也就不存放在task了.否則,從it->task中取得task.如果已經是最後一個task就必須要調用cgroup_advance_iter()填充迭代器裡面的資料.最後將取得的task返回.
cgroup_iter_end()用來對迭代碼進行收尾的工作,代碼如下:
void cgroup_iter_end(struct cgroup *cgrp, struct cgroup_iter *it)
{
read_unlock(&css_set_lock);
}
它就是釋放了在cgroup_iter_start()中持有的鎖.
回到cgroup_tasks_open()中.我們接下來會遇到kernel為sequential file提供的一組接口.首先在代碼遇到的是seq_open().代碼如下:
int seq_open(struct file *file, const struct seq_operations *op)
{
struct seq_file *p = file->private_data;
if (!p) {
p = kmalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return -ENOMEM;
file->private_data = p;
}
memset(p, 0, sizeof(*p));
mutex_init(&p->lock);
p->op = op;
file->f_version = 0;
/* SEQ files support lseek, but not pread/pwrite */
file->f_mode &= ~(FMODE_PREAD | FMODE_PWRITE);
return 0;
}
從代碼中可以看出,它就是初始化了一個struct seq_file結構.並且將其關聯到file->private_data.在這裡要注意將seq_file->op設置成了參數op.在我們分析的這個情景中,也就是cgroup_tasks_seq_operations.這個在我們分析文件的讀操作的時候會用到的.
7.5.2:task文件的read操作
從上面的代碼中可看到.在open的時候,更改了file->f_op.將其指向了cgroup_tasks_operations.該結構如下:
static struct file_operations cgroup_tasks_operations = {
.read = seq_read,
.llseek = seq_lseek,
.write = cgroup_file_write,
.release = cgroup_tasks_release,
}
相應的,read操作就會轉入到seq_read()中.由於該函數篇幅較大,這裡就不列出了.感興趣的可以自己跟蹤看一下,其它就是循環調用seq_file->op->start() à seq_file->op->show() à seq_file->op->next() à seq_file->op->stop()的過程.
我們在上面分析task文件的open操作的時候,曾經提配過,seq_file->op被指向了cgroup_tasks_seq_operations.定義如下:
static struct seq_operations cgroup_tasks_seq_operations = {
.start = cgroup_tasks_start,
.stop = cgroup_tasks_stop,
.next = cgroup_tasks_next,
.show = cgroup_tasks_show,
}
Cgroup_tasks_start()代碼如下:
static void *cgroup_tasks_start(struct seq_file *s, loff_t *pos)
{
/*
* Initially we receive a position value that corresponds to
* one more than the last pid shown (or 0 on the first call or
* after a seek to the start). Use a binary-search to find the
* next pid to display, if any
*/
struct cgroup *cgrp = s->private;
int index = 0, pid = *pos;
int *iter;
down_read(&cgrp->pids_mutex);
if (pid) {
int end = cgrp->pids_length;
while (index < end) {
int mid = (index + end) / 2;
if (cgrp->tasks_pids[mid] == pid) {
index = mid;
break;
} else if (cgrp->tasks_pids[mid] <= pid)
index = mid + 1;
else
end = mid;
}
}
/* If we're off the end of the array, we're done */
if (index >= cgrp->pids_length)
return NULL;
/* Update the abstract position to be the actual pid that we found */
iter = cgrp->tasks_pids + index;
*pos = *iter;
return iter;
}
它以二分法從cgrp->tasks_pids[ ]中去尋找第一個大於或者等於參數*pos值的項.如果找到了,返回該項.如果沒找到.返回NULL.
cgroup_tasks_show()代碼如下:
static int cgroup_tasks_show(struct seq_file *s, void *v)
{
return seq_printf(s, "%d\n", *(int *)v);
}
它就是將pid轉換為了字符串.
cgroup_tasks_next()就是找到數組中的下一項.代碼如下:
static void *cgroup_tasks_next(struct seq_file *s, void *v, loff_t *pos)
{
struct cgroup *cgrp = s->private;
int *p = v;
int *end = cgrp->tasks_pids + cgrp->pids_length;
/*
* Advance to the next pid in the array. If this goes off the
* end, we're done
*/
p++;
if (p >= end) {
return NULL;
} else {
*pos = *p;
return p;
}
}
cgroup_tasks_stop()代碼如下:
static void cgroup_tasks_stop(struct seq_file *s, void *v)
{
struct cgroup *cgrp = s->private;
up_read(&cgrp->pids_mutex);
}
它只是釋放了在cgroup_tasks_start()中持有的讀寫鎖.
7.5.3:task文件的close操作
Task文件close時,調用的相應接口為cgroup_tasks_release().代碼如下:
static int cgroup_tasks_release(struct inode *inode, struct file *file)
{
struct cgroup *cgrp = __d_cgrp(file->f_dentry->d_parent);
if (!(file->f_mode & FMODE_READ))
return 0;
release_cgroup_pid_array(cgrp);
return seq_release(inode, file);
}
它就是將cgroup中的pid信息與seqfile信息釋放掉.
到這裡,我們已經分析完了task文件的open,read,close操作.我們現在就可以實現一下,看上面的分析是否正確.
在前面已經分析中cgroupfs_root.top_cgroup會將系統中的所有css_set與之關聯起來,那麼通過cgroupfs_root_top_cgroup找到的process應該是系統當前的所有process.那麼相應的,在掛載目錄的task文件的內容.應該是系統中所有process的pid.
如下所示:
[root@localhost cgroup]# cat tasks
1
2
3
………
………
2578
其實,這樣做是cgroup子系統開發者特意設置的.它表示所有的process都在hierarchy的控制之下.
反過來,當我們在掛載目錄mkdir一個目錄,它下面的task文件內容應該是空的.因為在mkdir後,它對應的cgroup並沒有關聯任何task.
如下所示:
[root@localhost cgroup]# mkdir eric
[root@localhost cgroup]# cat eric/tasks
[root@localhost cgroup]#
下面我們來看一下task文件的寫操作,也就是怎樣將process添加進cgroup.
7.5.4:task文件的write操作
根據上面的文件,可得知task文件的write操作對應的函數為int cgroup_tasks_write().代碼如下:
static int cgroup_tasks_write(struct cgroup *cgrp, struct cftype *cft, u64 pid)
{
int ret;
/*如果cgroup已經被移除了,非法*/
if (!cgroup_lock_live_group(cgrp))
return -ENODEV;
/*將PID為pid的process與cgroup關聯*/
ret = attach_task_by_pid(cgrp, pid);
cgroup_unlock();
return ret;
}
Attach_task_by_pid()的代碼如下:
static int attach_task_by_pid(struct cgroup *cgrp, u64 pid)
{
struct task_struct *tsk;
int ret;
/*如果pid不為0.尋找PID為pid的task.並增加其引用計數*/
if (pid) {
rcu_read_lock();
tsk = find_task_by_vpid(pid);
if (!tsk || tsk->flags & PF_EXITING) {
rcu_read_unlock();
return -ESRCH;
}
get_task_struct(tsk);
rcu_read_unlock();
if ((current->euid) && (current->euid != tsk->uid)
&& (current->euid != tsk->suid)) {
put_task_struct(tsk);
return -EACCES;
}
}
/*如果pid為0.表示是將當前process添加進cgroup*/
else {
tsk = current;
get_task_struct(tsk);
}
/*將cgroup與task相關聯*/
ret = cgroup_attach_task(cgrp, tsk);
/*操作完成,減少其引用計數*/
put_task_struct(tsk);
return ret;
}
如果寫入的是一個不這0的數,表示的是process的PID值.如果是寫入0,表示是將當前process.這個操作的核心操作是cgroup_attach_task().代碼如下:
int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
{
int retval = 0;
struct cgroup_subsys *ss;
struct cgroup *oldcgrp;
struct css_set *cg = tsk->cgroups;
struct css_set *newcg;
struct cgroupfs_root *root = cgrp->root;
int subsys_id;
/*得到與cgroup關聯的第一個subsys的序號*/
get_first_subsys(cgrp, NULL, &subsys_id);
/* Nothing to do if the task is already in that cgroup */
/*找到這個process之前所屬的cgroup*/
oldcgrp = task_cgroup(tsk, subsys_id);
/*如果已經在這個cgrp裡面了.*/
if (cgrp == oldcgrp)
return 0;
/* 遍歷與hierarchy關聯的subsys
* 如果subsys定義了can_attach函數,就調用它
*/
for_each_subsys(root, ss) {
if (ss->can_attach) {
retval = ss->can_attach(ss, cgrp, tsk);
if (retval)
return retval;
}
}
/*
* Locate or allocate a new css_set for this task,
* based on its final set of cgroups
*/
/*找到這個task所關聯的css_set.如果不存在,則新建一個*/
newcg = find_css_set(cg, cgrp);
if (!newcg)
return -ENOMEM;
task_lock(tsk);
/*如果task正在執行exit操作*/
if (tsk->flags & PF_EXITING) {
task_unlock(tsk);
put_css_set(newcg);
return -ESRCH;
}
/*將tak->cgroup指向這個css_set*/
rcu_assign_pointer(tsk->cgroups, newcg);
task_unlock(tsk);
/* Update the css_set linked lists if we're using them */
/*更改task->cg_list*/
write_lock(&css_set_lock);
if (!list_empty(&tsk->cg_list)) {
list_del(&tsk->cg_list);
list_add(&tsk->cg_list, &newcg->tasks);
}
write_unlock(&css_set_lock);
/* 遍歷與hierarchy關聯的subsys
* 如果subsys定義了attach 函數,就調用它
*/
for_each_subsys(root, ss) {
if (ss->attach)
ss->attach(ss, cgrp, oldcgrp, tsk);
}
set_bit(CGRP_RELEASABLE, &oldcgrp->flags);
synchronize_rcu();
/*減小舊指向的引用計數*/
put_css_set(cg);
return 0;
}
這個函數邏輯很清楚,它就是初始化task->cgroup.然後將它和subsys相關聯.可自行參照代碼中的註釋進行分析.這裡就不再贅述了.
在這裡,詳細分析一下find_css_set()函數,這個函數有點意思.代碼如下:
static struct css_set *find_css_set(
struct css_set *oldcg, struct cgroup *cgrp)
{
struct css_set *res;
struct cgroup_subsys_state *template[CGROUP_SUBSYS_COUNT];
int i;
struct list_head tmp_cg_links;
struct cg_cgroup_link *link;
struct hlist_head *hhead;
/* First see if we already have a cgroup group that matches
* the desired set */
read_lock(&css_set_lock);
/*尋找從oldcg轉換為cgrp的css_set.如果不存在,返回NULL */
res = find_existing_css_set(oldcg, cgrp, template);
/*如果css_set已經存在,增加其引用計數後退出*/
if (res)
get_css_set(res);
read_unlock(&css_set_lock);
if (res)
return res;
這一部份,先從哈希數組中搜索從oldcg轉換cgrp的css_set.如果不存在,返回NULL.如果在哈希數組中存放,增加其引用計數返回即可.
Find_existing_css_set()的代碼如下:
static struct css_set *find_existing_css_set(
struct css_set *oldcg,
struct cgroup *cgrp,
struct cgroup_subsys_state *template[])
{
int i;
struct cgroupfs_root *root = cgrp->root;
struct hlist_head *hhead;
struct hlist_node *node;
struct css_set *cg;
/* Built the set of subsystem state objects that we want to
* see in the new css_set */
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
if (root->subsys_bits & (1UL << i)) {
/* Subsystem is in this hierarchy. So we want
* the subsystem state from the new
* cgroup */
template[i] = cgrp->subsys[i];
} else {
/* Subsystem is not in this hierarchy, so we
* don't want to change the subsystem state */
template[i] = oldcg->subsys[i];
}
}
hhead = css_set_hash(template);
hlist_for_each_entry(cg, node, hhead, hlist) {
if (!memcmp(template, cg->subsys, sizeof(cg->subsys))) {
/* All subsystems matched */
return cg;
}
}
/* No existing cgroup group matched */
return NULL;
}
如果subsys與新的cgroup相關聯,那麼它指向新的cgroup->subsys[]中的對應項.否則指向舊的cgrop的對應項.這樣做主要是因為,該process可能還被關聯在其它的hierarchy中.所以要保持它在其它hierarchy中的信息.
最後,在css_set_table[ ]中尋找看是否有與template相等的項.有的話返回該項.如果沒有.返回NULL.
/*分配一個css_set*/
res = kmalloc(sizeof(*res), GFP_KERNEL);
if (!res)
return NULL;
/* Allocate all the cg_cgroup_link objects that we'll need */
/*分配root_count項cg_cgroup_link*/
if (allocate_cg_links(root_count, &tmp_cg_links) < 0) {
kfree(res);
return NULL;
}
/* 初始化剛分配的css_set */
atomic_set(&res->refcount, 1);
INIT_LIST_HEAD(&res->cg_links);
INIT_LIST_HEAD(&res->tasks);
INIT_HLIST_NODE(&res->hlist);
/* Copy the set of subsystem state objects generated in
* find_existing_css_set() */
/*設置css_set->subsys*/
memcpy(res->subsys, template, sizeof(res->subsys));
運行到這裡的話.表示沒有從css_set_table[ ]中找到相應項.因此需要分配並初始化一個css_set結構.並且設置css_set的subsys域.
write_lock(&css_set_lock);
/* Add reference counts and links from the new css_set. */
/*遍歷所有的subsys以及css_set 中的subsys[ ].
*建立task所在的cgroup到css_set的引用
*/
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup *cgrp = res->subsys[i]->cgroup;
struct cgroup_subsys *ss = subsys[i];
atomic_inc(&cgrp->count);
/*
* We want to add a link once per cgroup, so we
* only do it for the first subsystem in each
* hierarchy
*/
if (ss->root->subsys_list.next == &ss->sibling) {
BUG_ON(list_empty(&tmp_cg_links));
link = list_entry(tmp_cg_links.next,
struct cg_cgroup_link,
cgrp_link_list);
list_del(&link->cgrp_link_list);
list_add(&link->cgrp_link_list, &cgrp->css_sets);
link->cg = res;
list_add(&link->cg_link_list, &res->cg_links);
}
}
/*似乎沒有地方會更改rootnode.subsys_list.?這裡的判斷大部份情況是滿足的*/
if (list_empty(&rootnode.subsys_list)) {
/*建立這個css_set到dumytop的引用*/
/* 這樣做,是為了讓新建的hierarchy能夠關聯到所有的process*/
link = list_entry(tmp_cg_links.next,
struct cg_cgroup_link,
cgrp_link_list);
list_del(&link->cgrp_link_list);
list_add(&link->cgrp_link_list, &dummytop->css_sets);
link->cg = res;
list_add(&link->cg_link_list, &res->cg_links);
}
BUG_ON(!list_empty(&tmp_cg_links));
這一部份的關鍵操作都在代碼中添加了相應的註釋.如果系統中存在多個hierarchy.那麼這個process肯定也位於其它的hierarchy所對應的cgroup中.因此需要在新分配的css_set中保存這些信息,也就是建立從cgroup到css_set的引用.
另外,關於ist_empty(&rootnode.subsys_list)的操作.似乎沒看到有什麼地方會更改rootnode.subsys_list.不過,如果rootnode.subsys_list不為空的話,也會在它前面的for循環中檢測出來.
總而言之.系統中有root_count個hierarchy.上述的引用保存過程就會進行root_count次.因此.到最後.tmp_cg_links肯定會空了.如果不為空.說明某處發生了錯誤.
/*增加css_set計數*/
css_set_count++;
/* Add this cgroup group to the hash table */
/*將其添加到全局哈希數組: css_set_table[ ]*/
hhead = css_set_hash(res->subsys);
hlist_add_head(&res->hlist, hhead);
write_unlock(&css_set_lock);
return res;
}
最後,將生成的css_set添加到哈希數組css_set_table[ ]中.
到這裡,task文件的操作已經分析完了.
7.6: notify_on_release文件操作
notify_on_release文件對應的cftype結構如下:
{
.name = "notify_on_release",
.read_u64 = cgroup_read_notify_on_release,
.write_u64 = cgroup_write_notify_on_release,
.private = FILE_NOTIFY_ON_RELEASE,
}
從此得知.文件的讀操作接口為cgroup_read_notify_on_release().代碼如下:
static u64 cgroup_read_notify_on_release(struct cgroup *cgrp,
struct cftype *cft)
{
return notify_on_release(cgrp);
}
繼續跟進notify_on_release().如下示:
static int notify_on_release(const struct cgroup *cgrp)
{
return test_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
}
從此可以看到,如果當前cgroup設置了CGRP_NOTIFY_ON_RELEASE標誌.就會返回1.否則.就是為0.
從當前系統中測試一下,如下:
[root@localhost cgroup]# cat notify_on_release
0
[root@localhost cgroup]#
文件內容為零.因為top_cgroup上沒有設置CGRP_NOTIFY_ON_RELEASE的標誌.
notify_on_release文件讀操作接口為cgroup_write_notify_on_release().代碼如下:
static int cgroup_write_notify_on_release(struct cgroup *cgrp,
struct cftype *cft,
u64 val)
{
clear_bit(CGRP_RELEASABLE, &cgrp->flags);
if (val)
set_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
else
clear_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
return 0;
}
從上面的代碼可以看到.如果我們寫入的是1.就會設置cgroup標誌的CGRP_NOTIFY_ON_RELEASE位.否則.清除CGRP_NOTIFY_ON_RELEASE位.測試如下:
[root@localhost cgroup]# echo 1 > notify_on_release
[root@localhost cgroup]# cat notify_on_release
1
[root@localhost cgroup]# echo 0 > notify_on_release
[root@localhost cgroup]# cat notify_on_release
0
[root@localhost cgroup]#
7.7: release_agent文件操作
release_agent只有在頂層目錄才會有.它所代表的cftype結構如下:
static struct cftype cft_release_agent = {
.name = "release_agent",
.read_seq_string = cgroup_release_agent_show,
.write_string = cgroup_release_agent_write,
.max_write_len = PATH_MAX,
.private = FILE_RELEASE_AGENT,
};
由此可以看到.讀文件的接口為cgroup_release_agent_show.代碼如下:
static int cgroup_release_agent_show(struct cgroup *cgrp, struct cftype *cft,
struct seq_file *seq)
{
if (!cgroup_lock_live_group(cgrp))
return -ENODEV;
seq_puts(seq, cgrp->root->release_agent_path);
seq_putc(seq, '\n');
cgroup_unlock();
return 0;
}
從代碼中可以看到.就是打印出root的release_agent_path.
寫文件的接口為cgroup_release_agent_write().如下示:
static int cgroup_release_agent_write(struct cgroup *cgrp, struct cftype *cft,
const char *buffer)
{
BUILD_BUG_ON(sizeof(cgrp->root->release_agent_path) < PATH_MAX);
if (!cgroup_lock_live_group(cgrp))
return -ENODEV;
strcpy(cgrp->root->release_agent_path, buffer);
cgroup_unlock();
return 0;
}
由此得知.往這個文件中寫內容,就是設置root的release_agent_path.如下做個測試:
[root@localhost cgroup]# cat release_agent
[root@localhost cgroup]# echo /bin/ls > release_agent
[root@localhost cgroup]# cat release_agent
/bin/ls
[root@localhost cgroup]#
7.8:debug創建的文件分析
下面分析一下debug subsys中的文件.由於我們掛載的時候沒有帶noprefix.因為.debug生成的文件都帶了一個」debug_」前綴.由debug創建的文件如下示:
debug.cgroup_refcount debug.current_css_set_refcount debug.taskcount debug.current_css_set debug.releasable
挨個分析如下:
7.8.1: cgroup_refcount文件操作
Cgroup_refcount所代表的cftype結構如下示:
{
.name = "cgroup_refcount",
.read_u64 = cgroup_refcount_read,
},
可以看到,該文件不能寫,只能讀.讀操作接口為cgroup_refcount_read().代碼如下:
static u64 cgroup_refcount_read(struct cgroup *cont, struct cftype *cft)
{
return atomic_read(&cont->count);
}
它就是顯示出當前cgroup的引用計數.
測試如下:
[root@localhost cgroup]# cat debug.cgroup_refcount
0
[root@localhost cgroup]#
頂層的cgroup是位於cgroupfs_root.top_cgroup.它的引用計數為0.
接下來,我們在下層創建一個子層cgroup.如下示:
[root@localhost cgroup]# mkdir /dev/cgroup/eric
[root@localhost cgroup]# cat /dev/cgroup/eric/debug.cgroup_refcount
0
[root@localhost cgroup]#
可見創建子層cgroup不會增加其引用計數.因為它只是與它的上一層cgroup構成指針指向關係.
現在我們讓子層cgroup關聯一個process
[root@localhost cgroup]# echo 1673 > /dev/cgroup/eric/tasks
[root@localhost cgroup]# cat /dev/cgroup/eric/debug.cgroup_refcount
1
[root@localhost cgroup]#
可以看到.它的計數比為了1.這裡在關聯process的css_set和所在的cgroup時增加的.
7.8.2: current_css_set文件操作
current_css_set對應的cftype結構如下示:
{
.name = "current_css_set",
.read_u64 = current_css_set_read,
},
可看出.它也是一個只讀的.讀接口為current_css_set_read().代碼如下:
static u64 current_css_set_read(struct cgroup *cont, struct cftype *cft)
{
return (u64)(long)current->cgroups;
}
它就是顯示了當前process關聯的css_set的地址.
測試如下:
[root@localhost cgroup]# cat debug.current_css_set
18446744072645980768
7.8.3: current_css_set_refcount文件操作
current_css_set_refcount文件對應的ctype結構如下:
{
.name = "current_css_set_refcount",
.read_u64 = current_css_set_refcount_read,
},
照例.它也是只讀的.接口如下:
static u64 current_css_set_refcount_read(struct cgroup *cont,
struct cftype *cft)
{
u64 count;
rcu_read_lock();
count = atomic_read(¤t->cgroups->refcount);
rcu_read_unlock();
return count;
}
它就是顯示出與當前process關聯的css_set的引用計數.
測試如下:
[root@localhost cgroup]# cat debug.current_css_set_refcount
56
表示已經有56個process關聯到這個css_set了.
7.8.3: taskcount文件操作
Taskcount文件對應cftype結構如下:
{
.name = "taskcount",
.read_u64 = taskcount_read,
},
只讀文件.接口如下:
static u64 taskcount_read(struct cgroup *cont, struct cftype *cft)
{
u64 count;
cgroup_lock();
count = cgroup_task_count(cont);
cgroup_unlock();
return count;
}
其中,子函數cgroup_task_count()我們在之前已經分析過了.它就是計算與當前cgroup關聯的process數目.這裡就不再分析了.測試如下:
[root@localhost cgroup]# cat debug.taskcount
56
7.8.4: releasable文件操作
Releasable文件對應的ctype結構如下示:
{
.name = "releasable",
.read_u64 = releasable_read,
},
只讀,讀接口代碼如下:
static u64 releasable_read(struct cgroup *cgrp, struct cftype *cft)
{
return test_bit(CGRP_RELEASABLE, &cgrp->flags);
}
它用來查看當前cgroup是否有CGRP_RELEASABLE標誌.如果有.顯示為1.否則顯示為0.
測試如下:
[root@localhost cgroup]# cat debug.releasable
0
經過上面的分析.可以知道.如果往cgroup中刪除一個關聯process,就會將其設置CGRP_RELEASABLE標誌.有下面測試:
[root@localhost cgroup]# mkdir eric
[root@localhost cgroup]# cat eric/debug.releasable
0
[root@localhost cgroup]# echo 1650 > eric/tasks
[root@localhost cgroup]# echo 1701 > eric/tasks
[root@localhost cgroup]# cat eric/debug.releasable
0
[root@localhost cgroup]# echo 1650 >tasks
[root@localhost cgroup]# cat eric/debug.releasable
1
到這裡為止,各subsys共有的文件和debug中的文件操作就已經分析完了.其它的subsys遠遠比debug要複雜.之後再給出專題分析.詳情請關注本站更新.*^_^*
九: notify_on_release操作
下面我們來分析在之前一直在忽略的一個問題.也就是涉及到CGRP_NOTIFY_ON_RELEASE標誌和root-> release_agent_path[]部份.
它的重用,就是在cgroup中最後的一個process離開(包括process退出.process關聯到其它同類型的cgroup),或者是在最後一個子層cgroup被移除的時候.就會調用用戶空間的一個程序.這個程序的路徑是在root-> release_agent_path[]中指定的.
下面我們從代碼的角度來跟蹤一下.
9.1:process退出
我們在之前在分析父子process之間的cgroup關係的時候.忽略掉了__put_css_set函數中的一個部份.現在是時候來剝開它了.
次__put_css_set()被忽略的代碼片段列出,如下:
static void __put_css_set(struct css_set *cg, int taskexit)
{
......
......
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup *cgrp = cg->subsys[i]->cgroup;
if (atomic_dec_and_test(&cgrp->count) &&
notify_on_release(cgrp)) {
if (taskexit)
set_bit(CGRP_RELEASABLE, &cgrp->flags);
check_for_release(cgrp);
}
}
......
......
}
首先,process退出時,調用__put_css_set時.taskexit參數是為1的,因此在這裡,它會將cgroup的flag的CGRP_RELEASABLE位置1.
atomic_dec_and_test(&cgrp->count)返回為真的話,說明process所屬的cgroup中已經沒有其它的process了.因此即將要退出的子process就是cgroup中的最後一個process.
notify_on_release(cgrp)代碼如下:
static int notify_on_release(const struct cgroup *cgrp)
{
return test_bit(CGRP_NOTIFY_ON_RELEASE, &cgrp->flags);
}
它用來判斷cgroup有沒有設定CGRP_NOTIFY_ON_RELEASE標誌
綜合上面的分析.如果cgroup中最後一個process退出.且cgroup設定了CGRP_NOTIFY_ON_RELEASE標誌.流程就會轉到check_for_release()中.該函數代碼如下:
static void check_for_release(struct cgroup *cgrp)
{
/* All of these checks rely on RCU to keep the cgroup
* structure alive */
if (cgroup_is_releasable(cgrp) && !atomic_read(&cgrp->count)
&& list_empty(&cgrp->children) && !cgroup_has_css_refs(cgrp)) {
/* Control Group is currently removeable. If it's not
* already queued for a userspace notification, queue
* it now */
int need_schedule_work = 0;
spin_lock(&release_list_lock);
if (!cgroup_is_removed(cgrp) &&
list_empty(&cgrp->release_list)) {
list_add(&cgrp->release_list, &release_list);
need_schedule_work = 1;
}
spin_unlock(&release_list_lock);
if (need_schedule_work)
schedule_work(&release_agent_work);
}
}
首先,在這裡必須要滿足以下四個條件才能繼續下去:
1:cgroup_is_releasable()返回1.
代碼如下:
static int cgroup_is_releasable(const struct cgroup *cgrp)
{
const int bits =
(1 << CGRP_RELEASABLE) |
(1 << CGRP_NOTIFY_ON_RELEASE);
return (cgrp->flags & bits) == bits;
}
它表示當前cgroup是含含有CGRP_RELEASABLE和CGRP_NOTIFY_ON_RELEASE標誌.結合我們在上面分析的. CGRP_RELEASABLE標誌是process在退出是就會設置的.
2:cgroup的引用計數為0
3:cgroup沒有子層cgroup
4: cgroup_has_css_refs()返回0.代碼如下:
static int cgroup_has_css_refs(struct cgroup *cgrp)
{
int i;
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
struct cgroup_subsys_state *css;
/* Skip subsystems not in this hierarchy */
if (ss->root != cgrp->root)
continue;
css = cgrp->subsys[ss->subsys_id];
if (css && atomic_read(&css->refcnt))
return 1;
}
return 0;
}
也就是說,cgroup關聯的css_set引用計數必須要為0
滿足上面幾個條件之後.就說明該cgroup是可以釋放的.因此將cgroup鏈接到了release_list.接著調度了工作隊列.在工作隊列中會完成餘下的工作.
下面跟蹤看看這個工作隊列是怎麼處理餘下任務的.
release_agent_work定義如下:
static DECLARE_WORK(release_agent_work, cgroup_release_agent);
該工作隊列對應的處理函數為cgroup_release_agent().代碼如下:
static void cgroup_release_agent(struct work_struct *work)
{
BUG_ON(work != &release_agent_work);
mutex_lock(&cgroup_mutex);
spin_lock(&release_list_lock);
/*遍歷鏈表,直到其為空*/
while (!list_empty(&release_list)) {
char *argv[3], *envp[3];
int i;
char *pathbuf = NULL, *agentbuf = NULL;
/*取得鏈表項對應的cgroup*/
struct cgroup *cgrp = list_entry(release_list.next,
struct cgroup,
release_list);
/*將cgroup從release_list中斷開*/
list_del_init(&cgrp->release_list);
spin_unlock(&release_list_lock);
/*將cgroup的路徑存放到pathbuf中*/
pathbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!pathbuf)
goto continue_free;
if (cgroup_path(cgrp, pathbuf, PAGE_SIZE) < 0)
goto continue_free;
/*agentbuf存放release_agent_path的內容*/
agentbuf = kstrdup(cgrp->root->release_agent_path, GFP_KERNEL);
if (!agentbuf)
goto continue_free;
/*初始化運行參數和環境變量*/
i = 0;
argv[i++] = agentbuf;
argv[i++] = pathbuf;
argv[i] = NULL;
i = 0;
/* minimal command environment */
envp[i++] = "HOME=/";
envp[i++] = "PATH=/sbin:/bin:/usr/sbin:/usr/bin";
envp[i] = NULL;
/* Drop the lock while we invoke the usermode helper,
* since the exec could involve hitting disk and hence
* be a slow process */
/*調用用戶空間的process*/
mutex_unlock(&cgroup_mutex);
call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
mutex_lock(&cgroup_mutex);
continue_free:
kfree(pathbuf);
kfree(agentbuf);
spin_lock(&release_list_lock);
}
spin_unlock(&release_list_lock);
mutex_unlock(&cgroup_mutex);
}
該函數遍歷release_list中的cgroup.然後以其路徑做為參數.調用root->release_agent_path對應的程序.
我們來做如下的實驗:
為了配合這次實驗.必須要寫兩個測試的程序.代碼如下:
Test.c
#include <stdio.h>
#include <stdlib.h>
main()
{
int i = 30;
while(i){
i--;
sleep(1);
}
}
這個process睡眠30s之後退出.編譯成test
另外一個程序代碼如下:
Main.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
char buf[125] = "";
int i = 0;
sprintf(buf,"rm -f /var/eric_test");
system(buf);
while(i < argc){
sprintf(buf,"echo %s >> /var/eric_test",argv[i]);
system(buf);
i++;
}
}
它就是將調用參數輸出到/var/eric_test下面.
下面就可以開始我們的測試了.掛載目錄下已經有一個子層cgroup.如下示:
.
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- eric
| |-- debug.cgroup_refcount
| |-- debug.current_css_set
| |-- debug.current_css_set_refcount
| |-- debug.releasable
| |-- debug.taskcount
| |-- notify_on_release
| `-- tasks
|-- notify_on_release
|-- release_agent
`-- tasks
接下來設置realesse_agent_path和CGRP_NOTIFY_ON_RELEASE標誌,指令如下:
[root@localhost cgroup]# echo /root/main > release_agent
[root@localhost cgroup]# echo 1 > eric/notify_on_release
下面往子層cgroup中添加一個process.指令如下:
[root@localhost cgroup]# /root/test &
[1] 4350
[root@localhost cgroup]# echo 4350 > eric/tasks
[root@localhost cgroup]#
[1]+ Done /root/test
等/root/test運行完之後.就會進行notify_on_release的操作了.印證一下:
[root@localhost cgroup]# cat /var/eric_test
/root/main
/eric
一切都如我們上面分析的一樣
9.2:取消process與cgroup的關聯
當cgroup中的最後一個process取消關聯的時候,也會有notify_on_release過程.見下面的代碼片段:
int cgroup_attach_task(struct cgroup *cgrp, struct task_struct *tsk)
{
int retval = 0;
struct cgroup_subsys *ss;
struct cgroup *oldcgrp;
struct css_set *cg = tsk->cgroups;
......
......
set_bit(CGRP_RELEASABLE, &oldcgrp->flags);
synchronize_rcu();
put_css_set(cg);
}
這個函數我們在之前分析過,不過也把notify_on_release的過程去掉了.現在也把它加上.
代碼中的cg是指向process原本所引用的css_set
Oldcgrp是過程之前所在的cgroup
在代碼中,會將oldcgrp標誌設為CGRP_RELEASABLE.之後也會調用put_css_set().put_css_set()就是我們在上面分析的過程了.如果cgroup為空的話,就會產生notify_on_release的操作.
同樣做個測試:
接著上面的測試環境.我們先來看下環境下的相關文件內容:
[root@localhost cgroup]# cat release_agent
/root/main
[root@localhost cgroup]# cat eric/tasks
[root@localhost cgroup]# cat eric/notify_on_release
1
[root@localhost cgroup]# pwd
/dev/cgroup
好了,測試開始了:
[root@localhost cgroup]# rm -rf /var/eric_test
[root@localhost cgroup]# echo 1701 > eric/tasks
[root@localhost cgroup]# echo 1701 >tasks
[root@localhost cgroup]# cat /var/eric_test
/root/main
/eric
在上面的測試過程中.為了避免影響測試效果.先將/var/eric_test文件刪了.然後將process1701關聯到eric所表示的cgroup.然後再把1701再加最上層cgroup.這樣就會造成eric下關聯process為空.相應的會發生notify_on_release過程.上面的測試也印證了這一說話.
9.3:移除cgroup
當移除cgroup下的最後一個子層cgroup時.也會發生notify_on_release.
看一下移除cgroup時的代碼片段:
static int cgroup_rmdir(struct inode *unused_dir, struct dentry *dentry)
{
......
......
set_bit(CGRP_RELEASABLE, &parent->flags);
check_for_release(parent);
......
}
代碼中,parent表示cgroup的上一層.在移除cgroup時,會設置上一層的cgroup標誌的CGRP_RELEASABLE位.然後流程同樣會轉入到check_for_release().這樣,如果上一層cgroup是空的話.就會生notify_on_release操作了.
測試如下:
還是用上層的測試環境.先來看一下初始環境:
[root@localhost cgroup]# pwd
/dev/cgroup
[root@localhost cgroup]# cat release_agent
/root/main
[root@localhost cgroup]# cat eric/notify_on_release
1
在eric下面再加一層cgroup.
[root@localhost cgroup]# mkdir eric/test
[root@localhost cgroup]# tree
.
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- eric
| |-- debug.cgroup_refcount
| |-- debug.current_css_set
| |-- debug.current_css_set_refcount
| |-- debug.releasable
| |-- debug.taskcount
| |-- notify_on_release
| |-- tasks
| `-- test
| |-- debug.cgroup_refcount
| |-- debug.current_css_set
| |-- debug.current_css_set_refcount
| |-- debug.releasable
| |-- debug.taskcount
| |-- notify_on_release
| `-- tasks
|-- notify_on_release
|-- release_agent
`-- tasks
2 directories, 22 files
接著運行如下指令:
[root@localhost cgroup]# rm -rf /var/eric_test
[root@localhost cgroup]# rmdir eric/test/
[root@localhost cgroup]# cat /var/eric_test
/root/main
/eric
如上所示.把eric下的唯一一個cgroup移除的時候.就發生了notity_on_release過程.
十:cgroup的proc節點
10.1:/proce/cgroups
在前面分析cgroup初始化的時候.在cgroup_init()中有下面代碼片段:
int __init cgroup_init(void)
{
......
......
proc_create("cgroups", 0, NULL, &proc_cgroupstats_operations)
......
......
}
也就是說.會在proc根目錄下創建一個名為cgroups的文件.如下示:
[root@localhost cgroup]# ls /proc/cgroups
/proc/cgroups
接下來就來分析這個文件的操作.
該文件對應的操作集為
proc_cgroupstats_operations.定義如下:
static struct file_operations proc_cgroupstats_operations = {
.open = cgroupstats_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
}
從上面看到,這個文件是只讀的.
先來看open時的操作,對應接口為cgroupstats_open.代碼如下:
static int cgroupstats_open(struct inode *inode, struct file *file)
{
return single_open(file, proc_cgroupstats_show, NULL);
}
Single_open()函數十分簡單.它也是sequences file中提供的一個接口.有關sequences file部份我們在上面已經分析過了. 這裡就不再詳細分析了.它將seq_file的show操作指向了proc_cgroupstats_show.
我們在上面的proc_cgroupstats_operations結構中可看到,它提供的read操作為seq_read().它就是調用seq_file中的相關操作.在open的時候,已經將seq_file的show接口指向了proc_cgroupstats_show().代碼如下:
static int proc_cgroupstats_show(struct seq_file *m, void *v)
{
int i;
seq_puts(m, "#subsys_name\thierarchy\tnum_cgroups\tenabled\n");
mutex_lock(&cgroup_mutex);
for (i = 0; i < CGROUP_SUBSYS_COUNT; i++) {
struct cgroup_subsys *ss = subsys[i];
seq_printf(m, "%s\t%lu\t%d\t%d\n",
ss->name, ss->root->subsys_bits,
ss->root->number_of_cgroups, !ss->disabled);
}
mutex_unlock(&cgroup_mutex);
return 0;
}
從代碼中看到,它就是將系統中每subsys名稱.所在hierarchy的位碼. Hierarchy下面的cgroup數目和subsys的啟用狀態.
測試如下:
[root@localhost cgroup]# cat /proc/cgroups
#subsys_name hierarchy num_cgroups enabled
cpuset 0 1 1
debug 2 2 1
ns 0 1 1
cpuacct 0 1 1
memory 0 1 1
devices 0 1 1
freezer 0 1 1
從這裡可以看到所有的subsys和hierarchy的情況.在上面顯示的debug和其它的subsys不同.是因為用的是之前測試notify_on_release的環境.如下示:
[root@localhost cgroup]# tree ../cgroup/
../cgroup/
|-- debug.cgroup_refcount
|-- debug.current_css_set
|-- debug.current_css_set_refcount
|-- debug.releasable
|-- debug.taskcount
|-- eric
| |-- debug.cgroup_refcount
| |-- debug.current_css_set
| |-- debug.current_css_set_refcount
| |-- debug.releasable
| |-- debug.taskcount
| |-- notify_on_release
| `-- tasks
|-- notify_on_release
|-- release_agent
`-- tasks
1 directory, 15 files
10.2:proc下process鏡像中的cgroup
除了在proc頂層目錄創建cgroup外.另外在每個process鏡像下都有一個cgroup的文件.如下示:
[root@localhost cgroup]# ls /proc/648/cgroup
/proc/648/cgroup
來看一下這個文件對應的操作,如下示:
static const struct pid_entry tid_base_stuff[] = {
......
......
#ifdef CONFIG_CGROUPS
REG("cgroup", S_IRUGO, cgroup),
#endif
......
}
#define REG(NAME, MODE, OTYPE) \
NOD(NAME, (S_IFREG|(MODE)), NULL, \
&proc_##OTYPE##_operations, {})
從上面可以看到.Cgroup對應的操作為&proc_cgroup_operations
定義如下:
struct file_operations proc_cgroup_operations = {
.open = cgroup_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
Open對應的操作為cgroup_open.定義如下:
static int cgroup_open(struct inode *inode, struct file *file)
{
struct pid *pid = PROC_I(inode)->pid;
return single_open(file, proc_cgroup_show, pid);
}
又見到single_open()了.如上面的分析一樣,read操作的時候會轉入到proc_cgroup_show().代碼如下:
static int proc_cgroup_show(struct seq_file *m, void *v)
{
struct pid *pid;
struct task_struct *tsk;
char *buf;
int retval;
struct cgroupfs_root *root;
retval = -ENOMEM;
buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
goto out;
retval = -ESRCH;
pid = m->private;
tsk = get_pid_task(pid, PIDTYPE_PID);
if (!tsk)
goto out_free;
retval = 0;
mutex_lock(&cgroup_mutex);
/*遍歷所有的cgroupfs_root*/
for_each_root(root) {
struct cgroup_subsys *ss;
struct cgroup *cgrp;
int subsys_id;
int count = 0;
/* Skip this hierarchy if it has no active subsystems */
/*如果hierarchy中沒有subsys.就繼續下一個rootnode就是這樣的情況*/
if (!root->actual_subsys_bits)
continue;
/*打印hierarchy中的subsys位圖*/
seq_printf(m, "%lu:", root->subsys_bits);
/*打印hierarchy中的subsys名稱*/
for_each_subsys(root, ss)
seq_printf(m, "%s%s", count++ ? "," : "", ss->name);
seq_putc(m, ':');
/*process所在cgroup的path*/
get_first_subsys(&root->top_cgroup, NULL, &subsys_id);
cgrp = task_cgroup(tsk, subsys_id);
retval = cgroup_path(cgrp, buf, PAGE_SIZE);
if (retval < 0)
goto out_unlock;
seq_puts(m, buf);
seq_putc(m, '\n');
}
out_unlock:
mutex_unlock(&cgroup_mutex);
put_task_struct(tsk);
out_free:
kfree(buf);
out:
return retval;
}
它的核心操作在這個for循環中,它的操作在註釋中已經詳細的說明了.在這裡不做詳細分析.
我將虛擬機重啟了 *^_^*,所以現在的環境不是我們之前的測試環境了
測試一下:
[root@localhost ~]# cat /proc/646/cgroup
[root@localhost ~]#
說明當前系統中還沒有hierarchy.
接下來掛載上一個:
[root@localhost ~]# mkdir /dev/cgroup
[root@localhost ~]# mount -t cgroup cgroup -o debug /dev/cgroup/
[root@localhost ~]# cat /proc/6
6/ 609/ 646/
[root@localhost ~]# cat /proc/646/cgroup
2:debug:/
[root@localhost ~]#
從上面可以看到.系統已經有一個hierarchy.且綁定的是debug subsys.當前process是位於它的頂層.
繼續測試:
[root@localhost ~]# mkdir /dev/cgroup/eric
[root@localhost ~]# echo 646 > /dev/cgroup/eric/tasks
[root@localhost ~]# cat /proc/646/cgroup
2:debug:/eric
[root@localhost ~]#
可以看到,當前process是位於eric這個cgroup中.
十一:小結
在這一節裡,用大篇幅詳細的描述了整個cgroup的框架.cgroup框架並不複雜,只是其中的資料結構和大量的全局變量弄的頭昏眼花.因此理順這些資料結構和變量是閱讀cgroup代碼的關鍵.另外在cgroup中對於RCU和rw_mutex的使用也有值得推敲的地方.不過由於篇幅關係,就不再分析這一部份.在接下來專題裡.以cgroup框架為基礎來分析幾個重要的subsys.
]]>
Ubuntu Adobe Flash 中文亂碼解決法
http://blog.roodo.com/fauztech/archives/12280361.html
How to change Ubuntu loading splash/background in Ubuntu 10.04
]]>Note that memory usage on modern operating systems like Linux is an extremely complicated and difficult to understand area. In fact the chances of you actually correctly interpreting whatever numbers you get is extremely low. (Pretty much every time I look at memory usage numbers with other engineers, there is always a long discussion about what they actually mean that only results in a vague conclusion.)
First thing is to probably read the last part of this article which has some discussion of how memory is managed on Android:
http://android-developers.blogspot.com/2010/02/service-api-changes-starting-with.html
Now ActivityManager.getMemoryInfo() is our highest-level API for looking at overall memory usage. This is mostly there to help an application gauge how close the system is coming to having no more memory for background processes, thus needing to start killing needed processes like services. For pure Java applications, this should be of little use, since the Java heap limit is there in part to avoid one app from being able to stress the system to this point.
Going lower-level, you can use the Debug API to get raw kernel-level information about memory usage:http://developer.android.com/intl/de/reference/android/os/Debug.html#getMemoryInfo(android.os.Debug.MemoryInfo)
Note starting with 2.0 there is also an API, ActivityManager.getProcessMemoryInfo, to get this information about another process: http://developer.android.com/intl/de/reference/android/app/ActivityManager.html#getProcessMemoryInfo(int[])
This returns a low-level MemoryInfo structure with all of this data:
/** The proportional set size for dalvik. */
public int dalvikPss;
/** The private dirty pages used by dalvik. */
public int dalvikPrivateDirty;
/** The shared dirty pages used by dalvik. */
public int dalvikSharedDirty;
/** The proportional set size for the native heap. */
public int nativePss;
/** The private dirty pages used by the native heap. */
public int nativePrivateDirty;
/** The shared dirty pages used by the native heap. */
public int nativeSharedDirty;
/** The proportional set size for everything else. */
public int otherPss;
/** The private dirty pages used by everything else. */
public int otherPrivateDirty;
/** The shared dirty pages used by everything else. */
public int otherSharedDirty;
But as to what the difference is between "Pss", "PrivateDirty", and "SharedDirty"... well now the fun begins.
A lot of memory in Android (and Linux systems in general) is actually shared across multiple processes. So how much memory a processes uses is really not clear. Add on top of that paging out to disk (let alone swap which we don't use on Android) and it is even less clear.
Thus if you were to take all of the physical RAM actually mapped in to each process, and add up all of the processes, you would probably end up with a number much greater than the actual total RAM.
The Pss number is a metric the kernel computes that takes into account memory sharing -- basically each page of RAM in a process is scaled by a ratio of the number of other processes also using that page. This way you can (in theory) add up the pss across all processes to see the total RAM they are using, and compare pss between processes to get a rough idea of their relative weight.
The other interesting metric here is PrivateDirty, which is basically the amount of RAM inside the process that can not be paged to disk (it is not backed by the same data on disk), and is not shared with any other processes. Another way to look at this is the RAM that will become available to the system when that process goes away (and probably quickly subsumed into caches and other uses of it).
That is pretty much the SDK APIs for this. However there is more you can do as a developer with your device.
Using adb, there is a lot of information you can get about the memory use of a running system. A common one is the command "adb shell dumpsys meminfo" which will spit out a bunch of information about the memory use of each Java process, containing the above info as well as a variety of other things. You can also tack on the name or pid of a single process to see, for example "adb shell dumpsys meminfo system" give me the system process:
** MEMINFO in pid 890 [system] ** native dalvik other total size: 10940 7047 N/A 17987 allocated: 8943 5516 N/A 14459 free: 336 1531 N/A 1867 (Pss): 4585 9282 11916 25783 (shared dirty): 2184 3596 916 6696 (priv dirty): 4504 5956 7456 17916 Objects Views: 149 ViewRoots: 4 AppContexts: 13 Activities: 0 Assets: 4 AssetManagers: 4 Local Binders: 141 Proxy Binders: 158 Death Recipients: 49 OpenSSL Sockets: 0 SQL heap: 205 dbFiles: 0 numPagers: 0 inactivePageKB: 0 activePageKB: 0
The top section is the main one, where "size" is the total size in address space of a particular heap, "allocated" is the kb of actual allocations that heap things it has, "free" is the remaining kb free the heap has for additional allocations, and "pss" and "priv dirty" are the same as discussed before specific to pages associated with each of the heaps.
If you just want to look at memory usage across all processes, you can use the command "adb shell procrank". Output of this on the same system looks like:
PID Vss Rss Pss Uss cmdline 890 84456K 48668K 25850K 21284K system_server 1231 50748K 39088K 17587K 13792K com.android.launcher2 947 34488K 28528K 10834K 9308K com.android.wallpaper 987 26964K 26956K 8751K 7308K com.google.process.gapps 954 24300K 24296K 6249K 4824K com.android.phone 948 23020K 23016K 5864K 4748K com.android.inputmethod.latin 888 25728K 25724K 5774K 3668K zygote 977 24100K 24096K 5667K 4340K android.process.acore ... 59 336K 332K 99K 92K /system/bin/installd 60 396K 392K 93K 84K /system/bin/keystore 51 280K 276K 74K 68K /system/bin/servicemanager 54 256K 252K 69K 64K /system/bin/debuggerd
Here the Vss and Rss columns are basically noise (these are the straight-forward address space and RAM usage of a process, where if you add up the RAM usage across processes you get an ridiculously large number).
Pss is as we've seen before, and Uss is Priv Dirty.
Interesting thing to note here: Pss and Uss are slightly (or more than slightly) different than what we saw in meminfo. Why is that? Well procrank uses a different kernel mechanism to collect its data than meminfo does, and they give slightly different results. Why is that? Honestly I haven't a clue. I believe procrank may be the more accurate one... but really, this just leave the point: "take any memory info you get with a grain of salt; often a very large grain."
Finally there is the command "adb shell cat /proc/meminfo" that gives a summary of the overall memory usage of the system. There is a lot of data here, only the first few numbers worth discussing (and the remaining ones understood by few people, and my questions of those few people about them often resulting in conflicting explanations):
MemTotal: 395144 kB MemFree: 184936 kB Buffers: 880 kB Cached: 84104 kB SwapCached: 0 kB
MemTotal is the total amount of memory available to the kernel and user space (often less than the actual physical RAM of the device, since some of that RAM is needed for the radio, DMA buffers, etc).
MemFree is the amount of RAM that is not being used at all. The number you see here is very high; typically on an Android system this would be only a few MB, since we try to use available memory to keep processes running
Cached is the RAM being used for filesystem caches and other such things. Typical systems will need to have 20MB or so for this to avoid getting into bad paging states; the Android out of memory killer is tuned for a particular system to make sure that background processes are killed before the cached RAM is consumed too much by them to result in such paging.
Note that memory usage on modern operating systems like Linux is an extremely complicated and difficult to understand area. In fact the chances of you actually correctly interpreting whatever numbers you get is extremely low. (Pretty much every time I look at memory usage numbers with other engineers, there is always a long discussion about what they actually mean that only results in a vague conclusion.)
First thing is to probably read the last part of this article which has some discussion of how memory is managed on Android:
http://android-developers.blogspot.com/2010/02/service-api-changes-starting-with.html
Now ActivityManager.getMemoryInfo() is our highest-level API for looking at overall memory usage. This is mostly there to help an application gauge how close the system is coming to having no more memory for background processes, thus needing to start killing needed processes like services. For pure Java applications, this should be of little use, since the Java heap limit is there in part to avoid one app from being able to stress the system to this point.
Going lower-level, you can use the Debug API to get raw kernel-level information about memory usage:http://developer.android.com/intl/de/reference/android/os/Debug.html#getMemoryInfo(android.os.Debug.MemoryInfo)
Note starting with 2.0 there is also an API, ActivityManager.getProcessMemoryInfo, to get this information about another process: http://developer.android.com/intl/de/reference/android/app/ActivityManager.html#getProcessMemoryInfo(int[])
This returns a low-level MemoryInfo structure with all of this data:
/** The proportional set size for dalvik. */
public int dalvikPss;
/** The private dirty pages used by dalvik. */
public int dalvikPrivateDirty;
/** The shared dirty pages used by dalvik. */
public int dalvikSharedDirty;
/** The proportional set size for the native heap. */
public int nativePss;
/** The private dirty pages used by the native heap. */
public int nativePrivateDirty;
/** The shared dirty pages used by the native heap. */
public int nativeSharedDirty;
/** The proportional set size for everything else. */
public int otherPss;
/** The private dirty pages used by everything else. */
public int otherPrivateDirty;
/** The shared dirty pages used by everything else. */
public int otherSharedDirty;
But as to what the difference is between "Pss", "PrivateDirty", and "SharedDirty"... well now the fun begins.
A lot of memory in Android (and Linux systems in general) is actually shared across multiple processes. So how much memory a processes uses is really not clear. Add on top of that paging out to disk (let alone swap which we don't use on Android) and it is even less clear.
Thus if you were to take all of the physical RAM actually mapped in to each process, and add up all of the processes, you would probably end up with a number much greater than the actual total RAM.
The Pss number is a metric the kernel computes that takes into account memory sharing -- basically each page of RAM in a process is scaled by a ratio of the number of other processes also using that page. This way you can (in theory) add up the pss across all processes to see the total RAM they are using, and compare pss between processes to get a rough idea of their relative weight.
The other interesting metric here is PrivateDirty, which is basically the amount of RAM inside the process that can not be paged to disk (it is not backed by the same data on disk), and is not shared with any other processes. Another way to look at this is the RAM that will become available to the system when that process goes away (and probably quickly subsumed into caches and other uses of it).
That is pretty much the SDK APIs for this. However there is more you can do as a developer with your device.
Using adb, there is a lot of information you can get about the memory use of a running system. A common one is the command "adb shell dumpsys meminfo" which will spit out a bunch of information about the memory use of each Java process, containing the above info as well as a variety of other things. You can also tack on the name or pid of a single process to see, for example "adb shell dumpsys meminfo system" give me the system process:
** MEMINFO in pid 890 [system] ** native dalvik other total size: 10940 7047 N/A 17987 allocated: 8943 5516 N/A 14459 free: 336 1531 N/A 1867 (Pss): 4585 9282 11916 25783 (shared dirty): 2184 3596 916 6696 (priv dirty): 4504 5956 7456 17916 Objects Views: 149 ViewRoots: 4 AppContexts: 13 Activities: 0 Assets: 4 AssetManagers: 4 Local Binders: 141 Proxy Binders: 158 Death Recipients: 49 OpenSSL Sockets: 0 SQL heap: 205 dbFiles: 0 numPagers: 0 inactivePageKB: 0 activePageKB: 0
The top section is the main one, where "size" is the total size in address space of a particular heap, "allocated" is the kb of actual allocations that heap things it has, "free" is the remaining kb free the heap has for additional allocations, and "pss" and "priv dirty" are the same as discussed before specific to pages associated with each of the heaps.
If you just want to look at memory usage across all processes, you can use the command "adb shell procrank". Output of this on the same system looks like:
PID Vss Rss Pss Uss cmdline 890 84456K 48668K 25850K 21284K system_server 1231 50748K 39088K 17587K 13792K com.android.launcher2 947 34488K 28528K 10834K 9308K com.android.wallpaper 987 26964K 26956K 8751K 7308K com.google.process.gapps 954 24300K 24296K 6249K 4824K com.android.phone 948 23020K 23016K 5864K 4748K com.android.inputmethod.latin 888 25728K 25724K 5774K 3668K zygote 977 24100K 24096K 5667K 4340K android.process.acore ... 59 336K 332K 99K 92K /system/bin/installd 60 396K 392K 93K 84K /system/bin/keystore 51 280K 276K 74K 68K /system/bin/servicemanager 54 256K 252K 69K 64K /system/bin/debuggerd
Here the Vss and Rss columns are basically noise (these are the straight-forward address space and RAM usage of a process, where if you add up the RAM usage across processes you get an ridiculously large number).
Pss is as we've seen before, and Uss is Priv Dirty.
Interesting thing to note here: Pss and Uss are slightly (or more than slightly) different than what we saw in meminfo. Why is that? Well procrank uses a different kernel mechanism to collect its data than meminfo does, and they give slightly different results. Why is that? Honestly I haven't a clue. I believe procrank may be the more accurate one... but really, this just leave the point: "take any memory info you get with a grain of salt; often a very large grain."
Finally there is the command "adb shell cat /proc/meminfo" that gives a summary of the overall memory usage of the system. There is a lot of data here, only the first few numbers worth discussing (and the remaining ones understood by few people, and my questions of those few people about them often resulting in conflicting explanations):
MemTotal: 395144 kB MemFree: 184936 kB Buffers: 880 kB Cached: 84104 kB SwapCached: 0 kB
MemTotal is the total amount of memory available to the kernel and user space (often less than the actual physical RAM of the device, since some of that RAM is needed for the radio, DMA buffers, etc).
MemFree is the amount of RAM that is not being used at all. The number you see here is very high; typically on an Android system this would be only a few MB, since we try to use available memory to keep processes running
Cached is the RAM being used for filesystem caches and other such things. Typical systems will need to have 20MB or so for this to avoid getting into bad paging states; the Android out of memory killer is tuned for a particular system to make sure that background processes are killed before the cached RAM is consumed too much by them to result in such paging.
]]>
我使用的coLinux的核心版本為2.6.22.18,而Linux 在Kernels 2.6.16以後的版本對vm做了很大的調整,可透過/proc/sys/vm/drop_caches這個檔案來釋放記憶體。
1.釋放Linux記憶體(kernels 2.6.16以後的版本):
root@yesican:~# echo 1 > /proc/sys/vm/drop_caches
釋放dentries、inodes所用的 cache memory。
root@yesican:~# echo 2 > /proc/sys/vm/drop_caches
釋放pagecache、dentry、inode 所用的 cache memory。
root@yesican:~# echo 3 > /proc/sys/vm/drop_caches
完全釋放cache memory,必須先執行sync,避免錯誤。
root@yesican:~# sync
在釋放記憶體後再將/proc/sys/vm/drop_caches的值設為0。
root@yesican:~# echo 0 > /proc/sys/vm/drop_caches
2.釋放Linux swap 記憶體:(此例swap在/dev/cobd1)
root@yesican:~# swapoff /dev/cobd1;swapon /dev/cobd1
3.一些觀察記憶體狀況的常用指令:
root@yesican:~# free
root@yesican:~# vmstat
root@yesican:~# ps -aux
root@yesican:~# top
root@yesican:~# watch cat /proc/meminfo
參考資料:Drop Caches
Ref. :
● BroadcastReceiver | Android Developers
● BatteryManager | Android Developers
這裡介紹電池信息的取得.
android.content.BroadcastReceiver類
android.os.BatteryManager類
● 電池信息的取得,調用registerReceiver()方法。
第1個參數,設置BroadcastReceiver實例
第2個參數,設置追加了Intent.ACTION_BATTERY_CHANGED處理的IntentFilter實例。
● 在BroadcastReceiver的onReceive()事件,接收到的Intent.ACTION_BATTERY_CHANGED,包括下面的信息。
「status」(int類型)…狀態,定義值是BatteryManager.BATTERY_STATUS_XXX。
「health」(int類型)…健康,定義值是BatteryManager.BATTERY_HEALTH_XXX。
「present」(boolean類型)
「level」(int類型)…電池剩餘容量
「scale」(int類型)…電池最大值。通常為100。
「icon-small」(int類型)…圖標ID。
「plugged」(int類型)…連接的電源插座,定義值是BatteryManager.BATTERY_PLUGGED_XXX。
「voltage」(int類型)…mV。
「temperature」(int類型)…溫度,0.1度單位。例如 表示197的時候,意思為19.7度。
「technology」(String類型)…電池類型,例如,Li-ion等等。
001 |
package com.adakoda.batterytest; |
002 |
003 |
import android.app.Activity; |
004 |
import android.content.BroadcastReceiver; |
005 |
import android.content.Context; |
006 |
import android.content.Intent; |
007 |
import android.content.IntentFilter; |
008 |
import android.os.BatteryManager; |
009 |
import android.os.Bundle; |
010 |
import android.util.Log; |
011 |
012 |
public class BatteryTestActivity extends Activity { |
013 |
/** Called when the activity is first created. */ |
014 |
@Override |
015 |
public void onCreate(Bundle savedInstanceState) { |
016 |
super .onCreate(savedInstanceState); |
017 |
setContentView(R.layout.main); |
018 |
} |
019 |
020 |
@Override |
021 |
protected void onResume() { |
022 |
super .onResume(); |
023 |
|
024 |
IntentFilter filter = new IntentFilter(); |
025 |
|
026 |
filter.addAction(Intent.ACTION_BATTERY_CHANGED); |
027 |
registerReceiver(mBroadcastReceiver, filter); |
028 |
} |
029 |
030 |
@Override |
031 |
protected void onPause() { |
032 |
super .onPause(); |
033 |
|
034 |
unregisterReceiver(mBroadcastReceiver); |
035 |
} |
036 |
037 |
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { |
038 |
@Override |
039 |
public void onReceive(Context context, Intent intent) { |
040 |
String action = intent.getAction(); |
041 |
if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { |
042 |
int status = intent.getIntExtra( "status" , 0 ); |
043 |
int health = intent.getIntExtra( "health" , 0 ); |
044 |
boolean present = intent.getBooleanExtra( "present" , false ); |
045 |
int level = intent.getIntExtra( "level" , 0 ); |
046 |
int scale = intent.getIntExtra( "scale" , 0 ); |
047 |
int icon_small = intent.getIntExtra( "icon-small" , 0 ); |
048 |
int plugged = intent.getIntExtra( "plugged" , 0 ); |
049 |
int voltage = intent.getIntExtra( "voltage" , 0 ); |
050 |
int temperature = intent.getIntExtra( "temperature" , 0 ); |
051 |
String technology = intent.getStringExtra( "technology" ); |
052 |
|
053 |
String statusString = "" ; |
054 |
|
055 |
switch (status) { |
056 |
case BatteryManager.BATTERY_STATUS_UNKNOWN: |
057 |
statusString = "unknown" ; |
058 |
break ; |
059 |
case BatteryManager.BATTERY_STATUS_CHARGING: |
060 |
statusString = "charging" ; |
061 |
break ; |
062 |
case BatteryManager.BATTERY_STATUS_DISCHARGING: |
063 |
statusString = "discharging" ; |
064 |
break ; |
065 |
case BatteryManager.BATTERY_STATUS_NOT_CHARGING: |
066 |
statusString = "not charging" ; |
067 |
break ; |
068 |
case BatteryManager.BATTERY_STATUS_FULL: |
069 |
statusString = "full" ; |
070 |
break ; |
071 |
} |
072 |
|
073 |
String healthString = "" ; |
074 |
|
075 |
switch (health) { |
076 |
case BatteryManager.BATTERY_HEALTH_UNKNOWN: |
077 |
healthString = "unknown" ; |
078 |
break ; |
079 |
case BatteryManager.BATTERY_HEALTH_GOOD: |
080 |
healthString = "good" ; |
081 |
break ; |
082 |
case BatteryManager.BATTERY_HEALTH_OVERHEAT: |
083 |
healthString = "overheat" ; |
084 |
break ; |
085 |
case BatteryManager.BATTERY_HEALTH_DEAD: |
086 |
healthString = "dead" ; |
087 |
break ; |
088 |
case BatteryManager.BATTERY_HEALTH_OVER_VOLTAGE: |
089 |
healthString = "voltage" ; |
090 |
break ; |
091 |
case BatteryManager.BATTERY_HEALTH_UNSPECIFIED_FAILURE: |
092 |
healthString = "unspecified failure" ; |
093 |
break ; |
094 |
} |
095 |
|
096 |
String acString = "" ; |
097 |
|
098 |
switch (plugged) { |
099 |
case BatteryManager.BATTERY_PLUGGED_AC: |
100 |
acString = "plugged ac" ; |
101 |
break ; |
102 |
case BatteryManager.BATTERY_PLUGGED_USB: |
103 |
acString = "plugged usb" ; |
104 |
break ; |
105 |
} |
106 |
|
107 |
Log.v( "status" , statusString); |
108 |
Log.v( "health" , healthString); |
109 |
Log.v( "present" , String.valueOf(present)); |
110 |
Log.v( "level" , String.valueOf(level)); |
111 |
Log.v( "scale" , String.valueOf(scale)); |
112 |
Log.v( "icon_small" , String.valueOf(icon_small)); |
113 |
Log.v( "plugged" , acString); |
114 |
Log.v( "voltage" , String.valueOf(voltage)); |
115 |
Log.v( "temperature" , String.valueOf(temperature)); |
116 |
Log.v( "technology" , technology); |
117 |
} |
118 |
} |
119 |
}; |
120 |
} |
]]>
Android 的 build system 已經大幅將build的過程簡單化、自動化,所以可以在執行 envsetup.sh 之後使用各種方便
的指令,以下列出目前支援的功能:
Invoke ". build/envsetup.sh" from your shell to add the following functions to your environment:
- croot: Changes directory to the top of the tree.
- m: Makes from the top of the tree.
- mm: Builds all of the modules in the current directory.
- mmm: Builds all of the modules in the supplied directories.
- cgrep: Greps on all local C/C++ files.
- hgrep: Greps on all local C/C++ header files.
- jgrep: Greps on all local Java files.
- mkgrep: Greps on all local make files.
- rcgrep: Greps on all local .rc files.
- resgrep: Greps on all local res/*.xml files.
- shgrep: Greps on all local .sh files.
- godir: Go to the directory containing a file.
也可以在登入時自動執行此 script,編輯 ~/.bashrc 或其他 shell 的 rc 檔,加入此 script 即可
# invoke android envsetup.sh
source ~/android/build/envsetup.sh
這些功能可以協助處理大部分的例行性 build 功能,但有時候要 debug 或是特別需求時就不是那麼方便,
此時可以編輯 Anroid.mk 加入一些參數設定,可參考:
下面是一個 debug macro 時可能有用的範例:
如何知道 macro 展開的結果?
傳統來說,gcc 支援 -E 指令將 preprocessor 的展開結果產生出來:
#gcc –E MacroTest.c –o MacroTest_E.c
在 Android build system 中,預設使用的 arm-eabi-gcc 也支援這功能,但是要使用此功能就要可能
自己寫出又臭又長的的編譯指令,不如直接使用現有的 build system:
編輯 Android.mk
修改/加入:
LOCAL_CFLAGS := -E
使用mm編譯,結果會失敗,這是正常的,因為原本的 obj file 已經變成展開結果
可以檢查 out 底下的路徑的 obj 檔,應該已經是展開的結果啦
out/target/product/a1/obj/EXECUTABLES/xxxx_intermediates/
官方的 Build system 文件:
其實最早有一篇文件是講述 Android 整個 build system 的文件,但該文件適用版本大概只到1.5左右,不過還是可以參考一下。
連結:http://android.git.kernel.org/?p=platform/build.git;a=blob_plain;f=core/build-system.html
之後官方網頁也有放上一些比較簡單的參考項目,可在下面連結找到:
http://source.android.com/porting/build_system.html
參考資料:
很早的 Android build system 文件:
更新過,但比較精簡
Makefile 簡易教學: http://kevincrazy.pixnet.net/blog/post/29780477
GNU 的巨集展開功能 (C 語言): http://ccckmit.wikidot.com/gnumacro
]]>ASHMEM = Android shared memory, 由Goolge 設計的一種記憶體分享、分配機制,主要用於 Google 設計給 Android 使用的 IPC Binder。
主要的driver 可在 aosp 的 kernel/mm/ashmem.c 找到實作,user space 的部份則是主要在 IMemory 裡面處理。
下面是從網路上收集的資訊:
Android ashmem的實現方式
來源: http://blog.sina.com.cn/s/blog_606334a20100goei.html
ashmem是android的內存分配/共享機制,在dev目錄下對應的設備是/dev/ashmem,相比於傳統的內存分配機制,如malloc、anonymous/named mmap,其好處是提供了輔助內核內存回收算法的pin/unpin機制。
ashmme的典型用法是先打開設備文件,然後做mmap映射。
第一步通過調用ashmem_create_region函數,這個函數完成這幾件事:
1)fd = open("/dev/ashmem", O_RDWR);
2)ioctl(fd, ASHMEM_SET_NAME, region_name); // 這一步可選
3)ioctl(fd, ASHMEM_SET_SIZE, region_size);
第二步,應用程序一般會調用mmap來把ashmem分配的空間映射到進程空間:
mapAddr = mmap(NULL, pHdr->mapLength, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
應用程序還可以通過ioctl來pin和unpin某一段映射的空間,以提示內核的page cache算法可以把哪些頁面回收,這是一般mmap做不到的。
可以說ashmem以較小的代價(用戶需進行額外的ioctl調用來設置名字,大小,pin和unpin),獲得了一些內存使用的智能性。
ashmem本身實現也很小巧,只有不到700行。原因是借助了內核已經有的工具,例如shmem_file_setup(支撐文件),cache_shrinker(slab分配算法的頁面回收的回調函數)等。
如果ashmem不使用內核驅動實現,則pin/unpin的語義比較難以實現,或者即使實現,效率也不會很高。但查詢android源碼,使用pin/unpin很少,看來ashmem還是沒有很好地用起來。
如果不使用ashmem驅動,並且捨棄pin/unpin語義,那麼模擬ashmem的語義還是很簡單的。首 先,ashmem_create_region可以為進程創建一個唯一的文件(如進程名+時戳),打開,然後返回這個文件的fd;接著應用程序可以進性一 般的mmap操作了。如果不使用ashmem_create_region接口函數,那麼使用anonymous的mmap就可以了,但這種方式屬於正在 被丟棄的方式,而且並不是所有的系統都支持,比如Macos就不支持。
Android ashmem 語義的實現
來源: http://blog.sina.com.cn/s/blog_606334a20100gp7q.html
ashmem非常像是anonymous mmap,這或許是它的名字的由來吧。網上有人問pin和unpin到底是做什麼用的,似乎沒人回答。簡單的回答是:沒什麼用。深沉點的回答是:可以用來更有效地使用內存。具體講,當你覺得用ashmem分配的空間有部分似乎不大用得著時,你可以unpin這一塊空間,unpin後,內核可以把它對應的物理頁面回收,挪作他用。你並不用擔心以後進程對unpin的空間的訪問,因為回收了的內存,你還可以再次獲得(通過缺頁handler),因為unpin操作並不改變已經mmap的地址空間。所以說,pin/unpin純粹是內核內部的操作,不影響上層應用的語義。
如果不用ashmem驅動,不考慮pin/unpin語義,並且考慮到現有API的持續性(ashmem_create_region等),實現ashmem的語義雖然概念上不難,但還是有一些細節需要注意。
首先,ashmem不是用於進程間共享數據的,這是由驅動實現決定的。驅動把ashmem分配的地址空間賦給file結構的private,這就排除了進程間共享的可能性。
其次,ashmem的作用等同於anonymous mmap,純粹作分配空間用,你打開多少次/dev/ashmem設備(並mmap),你就獲得多少次(不同的)空間。這在同一個進程也是一樣的。這一點很重要。
舉例來說:
fd1 = open("/dev/ashmem", O_RDWR);
addr1 = mmap(0, size, ..., fd1, ...);
fd2 = open("/dev/ashmem", O_RDWR);
addr2 = mmap(0, size, ..., fd2, ...);
即使在同一個進程內,addr1和addr2也是不同的。用/dev/zero達不到這個效果,普通文件也不行。
所以,在使用普通文件模擬ashmem行為時,必須給每個文件起唯一的名字。這個技術可有很多選擇,比如使用開機後經歷的時間作為文件名的一部分。
do_initcalls 的原理
前言
macro 定義__define_initcall(level,fn)對於內核的初始化很重要,它指示編譯器在編譯的時候,將一系列初始化函數的起始地址值按照一定的順序
放在一個section中。在內核初始化階段,do_initcalls() 將按順序從該section中以函數指針的形式取出這些函數的起始地址,來依次完成相應
的初始化。由於內核某些部分的初始化需要依賴於其他某些部分的初始化的完成,因此這個順序排列常常非常重要。
下面將從__define_initcall(level,fn) macro 定義的代碼分析入手,依次分析名稱為initcall.init的section的結構,最後分析內核初始化函數
do_initcalls()是如何利用macro 定義__define_initcall(level,fn)及其相關的衍生的7個macro macro 定義,來實現內核某些部分的順序初始化的。
1、分析 __define_initcall(level,fn) macro 定義
1) 這個macro 的定義位於inlclude\linux\init.h中:
#define
__define_initcall(level,fn) \
static initcall_t
__initcall_##fn \
__attribute__((__section__(".initcall"
level ".init"))) \
= fn
其中
initcall_t 是一個函數指針類型:
typedef int
(*initcall_t)(void);
而屬性
__attribute__((__section__())) 則表示把對象放在一個這個由括號中的名稱所指代的section中。
所以這個macro
定義的的含義是:1) 聲明一個名稱為__initcall_##fn的函數
指針(其中##表示替換連接,);2) 將這個函數指針初始化為fn;3) 編譯
的時候需要把這個函數指針變量放置到名稱為 ".initcall" level ".init"
的section中(比如level="1",代表這個section的名稱是
".initcall1.init")。
2) 舉例:__define_initcall(6,
pci_init)
上述macro
呼叫的含義是:1) 聲明一個函數指針__initcall_pic_init = pci_init;
且 2)
這個指針變量__initcall_pic_init 需要放置到名稱為 .initcall6.init
的section中( 其實質就是將 這個函數pic_init的首地址放置到了這個
section中)。
3) 這個macro 一般並不直接使用,而是被定義成下述其他更簡單的7個衍生macro
這些衍生macro
macro 的定義也位於 inlclude\linux\Init.h 中:
#define
core_initcall(fn)
__define_initcall("1",fn)
#define
postcore_initcall(fn)
__define_initcall("2",fn)
#define
arch_initcall(fn)
__define_initcall("3",fn)
#define
subsys_initcall(fn)
__define_initcall("4",fn)
#define
fs_initcall(fn)
__define_initcall("5",fn)
#define
device_initcall(fn)
__define_initcall("6",fn)
#define
late_initcall(fn)
__define_initcall("7",fn)
因此通過macro
core_initcall() 來聲明的函數指針,將放置到名稱為
.initcall1.init的section中,而通過macro postcore_initcall() 來
聲明的函數指針,將放置到名稱為.initcall2.init的section中,
依次類推。
4) 舉例:device_initcall(pci_init)
解釋同上
1-2)。
2、與初始化呼叫有關section--initcall.init被分成了7個子section
1) 它們依次是.initcall1.init、.initcall2.init、...、.initcall7.init
2) 按照先後順序依次排列
3) 它們的定義在文件vmlinux.lds.S中
例如 對於i386+,在i386\kernel\vmlinux.lds.S中有:
__initcall_start =
.;
.initcall.init : {
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
}
__initcall_end =
.;
而在makefile
中有
LDFLAGS_vmlinux += -T
arch/$(ARCH)/kernel/vmlinux.lds.s
4) 在這7個section總的開始位置被標識為__initcall_start,
而在結尾被標識為__initcall_end。
3、 內核初始化函數do_basic_setup():
do_initcalls() 將從.initcall.init
中,也就是這7個section中依次取出所有的函數指針,並呼叫這些
函數指針所指向的函數,來完成內核的一些相關的初始化。
這個函數的定義位於init\main.c中:
extern initcall_t
__initcall_start, __initcall_end;
static void __init
do_initcalls(void)
{
initcall_t *call;
....
for
(call = &__initcall_start; call < &__initcall_end; call++)
{
....
(*call)();
....
}
....
}
這些函數指針指向的函數就是通過macro __define_initcall(level,fn)
賦值的函數fn,他們呼叫的順序就是放置在這些section中的順序,
這個順序很重要, 這就是這個macro __define_initcall(level,fn)的作用。
注意到,這裡__initcall_start
和 __initcall_end 就是section
initcall.init的頭和尾。
4、 歸納之
1) __define_initcall(level,fn)的作用就是指示編譯器把一些初始化函數
的指針(即:函數起始地址)按照順序放置一個名為 .initcall.init 的
section中,這個section又被分成了7個子section,它們按順序排列。
在內核初始化
階段,這些放置到這個section中的函數指針將供
do_initcalls() 按順序依次呼叫,來完成相應初始化。
2) 函數指針放置到的子section由macro 定義的level確定,對應level較小的
子section位於較前面。而位於同一個子section內的函數指針順序不定,
將由編譯器按照編譯的順序隨機指定。
3) 因此,如果你希望某個初始化函數在內核初始化階段就被呼叫,那麼你
就應該使用macro __define_initcall(level,fn)
或 其7個衍生macro 把這個
函數fn的對應的指針放置到按照初始化的順序放置到相關的 section 中。
同事,如果某個初始化函數fn_B需要依賴於另外一個初始化函數fn_A的
完成,那麼你應該把fn_B放在比fn_A對應的level值較大的子section中,
這樣,do_initcalls()將在fn_A之後呼叫fn_B。
****************************************************************
如果你希望某個初始化函數在內核初始化階段就被呼叫,那麼你
就應該使用macro __define_initcall(level,fn) 或 其7個衍生macro 來
把這個初始化函數fn的起始地址按照初始化的順序放置到相關的
section 中。 內核初始化時的do_initcalls()將從這個section
中按順序找到這些函數來執行。 如果你希望某個初始化函數在內核初始化階段就被呼叫,那麼你
就應該使用macro __define_initcall(level,fn) 或 其7個衍生macro 來
把這個初始化函數fn的起始地址按照初始化的順序放置到相關的
section 中。 內核初始化時的do_initcalls()將從這個section
中按順序找到這些函數來執行。
*****************************************************************
The Dalvik VM supports a variety of command-line arguments (use adb shell dalvikvm -help
to get a summary), but it's not possible to pass arbitrary arguments through the Android application runtime. It is, however, possible to affect the VM behavior through certain system properties.
For all of the features described below, you would set the system property with setprop
, issuing a shell command on the device like this:
adb shell setprop <name> <value>
The Android runtime must be restarted before the changes will take effect (adb shell stop; adb shell start
). This is because the settings are processed in the "zygote" process, which starts early and stays around "forever".
You may not be able to set this as an unprivileged user. You can use adb root
or run the su
command from the device shell on "userdebug" builds to become root first. When in doubt,
adb shell getprop <name>
will tell you if the setprop
took.
If you don't want the property to evaporate when the device reboots, add a line to /data/local.prop
that looks like:
<name> = <value>
Such changes will survive reboots, but will be lost if the data partition is wiped. (Hint: create a local.prop
on your workstation, then adb push local.prop /data
. Or, use one-liners like adb shell "echo name = value >> /data/local.prop"
-- note the quotes are important.)
JNI, the Java Native Interface, provides a way for code written in the Java programming language interact with native (C/C++) code. The extended JNI checks will cause the system to run more slowly, but they can spot a variety of nasty bugs before they have a chance to cause problems.
There are two system properties that affect this feature, which is enabled with the -Xcheck:jni
command-line argument. The first is ro.kernel.android.checkjni
. This is set by the Android build system for development builds. (It may also be set by the Android emulator unless the -nojni
flag is provided on the emulator command line.) Because this is an "ro." property, the value cannot be changed once the device has started.
To allow toggling of the CheckJNI flag, a second property, dalvik.vm.checkjni
, is also checked. The value of this overrides the value from ro.kernel.android.checkjni
.
If neither property is defined, or dalvik.vm.checkjni
is set to false
, the -Xcheck:jni
flag is not passed in, and JNI checks will be disabled.
To enable JNI checking:
adb shell setprop dalvik.vm.checkjni true
You can also pass JNI-checking options into the VM through a system property. The value set for dalvik.vm.jniopts
will be passed in as the -Xjniopts
argument. For example:
adb shell setprop dalvik.vm.jniopts forcecopy
For more information about JNI checks, see JNI Tips.
Dalvik VM supports the Java programming language "assert" statement. By default they are off, but the dalvik.vm.enableassertions
property provides a way to set the value for a -ea
argument.
The argument behaves the same as it does in other desktop VMs. You can provide a class name, a package name (followed by "..."), or the special value "all".
For example, this:
adb shell setprop dalvik.vm.enableassertions all
enables assertions in all non-system classes.
The system property is much more limited than the full command line. It is not possible to specify more than one -ea
entry, and there is no way to specify a -da
entry. There is presently no equivalent for -esa
/-dsa
.
The system tries to pre-verify all classes in a DEX file to reduce class load overhead, and performs a series of optimizations to improve runtime performance. Both of these are done by the dexopt
command, either in the build system or by the installer. On a development device, dexopt
may be run the first time a DEX file is used and whenever it or one of its dependencies is updated ("just-in-time" optimization and verification).
There are two command-line flags that control the just-in-time verification and optimization, -Xverify
and -Xdexopt
. The Android framework configures these based on the dalvik.vm.dexopt-flags
property.
If you set:
adb shell setprop dalvik.vm.dexopt-flags v=a,o=v
then the framework will pass -Xverify:all -Xdexopt:verified
to the VM. This enables verification, and only optimizes classes that successfully verified. This is the safest setting, and is the default.
You could also set dalvik.vm.dexopt-flags
to v=n
to have the framework pass -Xverify:none -Xdexopt:verified
to disable verification. (We could pass in -Xdexopt:all
to allow optimization, but that wouldn't necessarily optimize more of the code, since classes that fail verification may well be skipped by the optimizer for the same reasons.) Classes will not be verified by dexopt
, and unverified code will be loaded and executed.
Enabling verification will make the dexopt
command take significantly longer, because the verification process is fairly slow. Once the verified and optimized DEX files have been prepared, verification incurs no additional overhead except when loading classes that failed to pre-verify.
If your DEX files are processed with verification disabled, and you later turn the verifier on, application loading will be noticeably slower (perhaps 40% or more) as classes are verified on first use.
For best results you should force a re-dexopt of all DEX files when this property changes. You can do this with:
adb shell "rm /data/dalvik-cache/*"
This removes the cached versions of the DEX files. Remember to stop and restart the runtime (adb shell stop; adb shell start
).
(Previous version of the runtime supported the boolean dalvik.vm.verify-bytecode
property, but that has been superceded by dalvik.vm.dexopt-flags
.)
The current implementation of the Dalvik VM includes three distinct interpreter cores. These are referred to as "fast", "portable", and "debug". The "fast" interpreter is optimized for the current platform, and might consist of hand-optimized assembly routines. In constrast, the "portable" interpreter is written in C and expected to run on a broad range of platforms. The "debug" interpreter is a variant of "portable" that includes support for profiling and single-stepping.
The VM may also support just-in-time compilation. While not strictly a different interpreter, the JIT compiler may be enabled or disabled with the same flag. (Check the output of dalvikvm -help
to see if JIT compilation is enabled in your VM.)
The VM allows you to choose between "fast", "portable", and "jit" with an extended form of the -Xint
argument. The value of this argument can be set through the dalvik.vm.execution-mode
system property.
To select the "portable" interpreter, you would use:
adb shell setprop dalvik.vm.execution-mode int:portable
If the property is not specified, the most appropriate interpreter will be selected automatically. At some point this mechanism may allow selection of other modes, such as JIT compilation.
Not all platforms have an optimized implementation. In such cases, the "fast" interpreter is generated as a series of C stubs, and the result will be slower than the "portable" version. (When we have optimized versions for all popular architectures the naming convention will be more accurate.)
If profiling is enabled or a debugger is attached, the VM switches to the "debug" interpreter. When profiling ends or the debugger disconnects, the original interpreter is resumed. (The "debug" interpreter is substantially slower, something to keep in mind when evaluating profiling data.)
If the VM is built with WITH_DEADLOCK_PREDICTION
, the deadlock predictor can be enabled with the -Xdeadlockpredict
argument. (The output from dalvikvm -help
will tell you if the VM was built appropriately -- look for deadlock_prediction
on the Configured with:
line.) This feature tells the VM to keep track of the order in which object monitor locks are acquired. If the program attempts to acquire a set of locks in a different order from what was seen earlier, the VM logs a warning and optionally throws an exception.
The command-line argument is set based on the dalvik.vm.deadlock-predict
property. Valid values are off
to disable it (default), warn
to log the problem but continue executing, err
to cause a dalvik.system.PotentialDeadlockError
to be thrown from the monitor-enter
instruction, and abort
to have the entire VM abort.
You will usually want to use:
adb shell setprop dalvik.vm.deadlock-predict err
unless you are keeping an eye on the logs as they scroll by.
Please note that this feature is deadlock prediction, not deadlock detection -- in the current implementation, the computations are performed after the lock is acquired (this simplifies the code, reducing the overhead added to every mutex operation). You can spot a deadlock in a hung process by sending a kill -3
and examining the stack trace written to the log.
This only takes monitors into account. Native mutexes and other resources can also be the cause of deadlocks, but will not be detected by this.
Like other desktop VMs, when the Dalvik VM receives a SIGQUIT (Ctrl-\ or kill -3
), it dumps stack traces for all threads. By default this goes to the Android log, but it can also be written to a file.
The dalvik.vm.stack-trace-file
property allows you to specify the name of the file where the thread stack traces will be written. The file will be created (world writable) if it doesn't exist, and the new information will be appended to the end of the file. The filename is passed into the VM via the -Xstacktracefile
argument.
For example:
adb shell setprop dalvik.vm.stack-trace-file /tmp/stack-traces.txt
If the property is not defined, the VM will write the stack traces to the Android log when the signal arrives.
For performance reasons, the checksum on "optimized" DEX files is ignored. This is usually safe, because the files are generated on the device, and have access permissions that prevent modification.
If the storage on a device becomes unreliable, however, data corruption can occur. This usually manifests itself as a repeatable virtual machine crash. To speed diagnosis of such failures, the VM provides the -Xcheckdexsum
argument. When set, the checksums on all DEX files are verified before the contents are used.
The application framework will provide this argument during VM creation if the dalvik.vm.check-dex-sum
property is enabled.
To enable extended DEX checksum verification:
adb shell setprop dalvik.vm.check-dex-sum true
Incorrect checksums will prevent the DEX data from being used, and will cause errors to be written to the log file. If a device has a history of problems it may be useful to add the property to /data/local.prop
.
Note also that the dexdump
tool always verifies DEX checksums, and can be used to check for corruption in a large set of files.
2. Celadon. 地址:3671 5th Ave, San Diego, CA 92101. 裝潢和口味同樣有風味的泰國餐廳,紅咖哩的料理都很好吃。主餐$8~15。
3. Extraordinary Dessert. Hillcrest: 2929 Fifth Ave., San Diego , CA 92103. 619-294-2132. Downtown (小義大利): 1430 Union St., San Diego, CA 92101. 619-294-7001. 味覺以外,也讓人在視覺上心花怒放的甜點店。甜點$4~12。
4. The Original Pancake House. 3906 Convoy St, San Diego , CA 92111. 858-565-1740. 專門作鬆餅的早餐店,來這裡一定要嚐嚐他們的德國鬆餅。早餐$8~15。
5. Donovan’s Steak and Chop. 4340 La Jolla Village Dr., San Diego, CA 92122. 曾被評選為全美第一的高級牛排館。牛排$30~50。
>> 好吃,推薦~
6. Pho T Cali (花嘉麗) Restaurant. 7351 Clairemont Mesa Blvd. San Diego, CA 92111. 便宜的越南牛肉粉。主餐$5~7。
7. El Cotixan.
8. 市中心煤氣燈區:第四街到第六街,Broadway和Harbor Dr.之間的市區。
9. The Cottage: 吃Brunch, 在 La Jolla Downtown 7702 Fay Ave, Price Range: $8~13
10. Roppongi: Asian Fusion Style, 在La Jolla Downtown 875 Prospect St. Price Range 很大,最好趁4~6pm Happy Hour, Appeitizer & Shushi Roll 半價, 精緻又好吃! 多人去吃比較划算!
11. Chin's 御園: San Diego 台菜中算好吃的, 尤其它的紅燒牛肉麵跟酒釀湯圓
12. 小肥羊: 在Convoy, San Diego 終於有專門吃火鍋的店了, 一定要點它的肉片, 不腥也不老, 很讚!不過價格有點高!
>> 很貴,不過還不錯 :P
]]>在kernel中有很多__init,這個東東到底是何方神聖捏?且聽小生我一一道來。
下面是其定義:
file:/include/linux/init.h
43 #define __init __attribute__ ((__section__ (".init.text"))) __cold
44 #define __initdata __attribute__ ((__section__ (".init.data")))
45 #define __exitdata __attribute__ ((__section__(".exit.data")))
46 #define __exit_call __attribute_used__ __attribute__ ((__section__ (".exitcall.exit")))
也許你會問那 __attribute__ ((__section__ (".init.text"))) __cold是什麼東東阿?
且看 info gcc C Extensions Attribute Syntax
section ("SECTION-NAME")'
Normally, the compiler places the objects it generates in sections
like `data' and `bss'. Sometimes, however, you need additional
sections, or you need certain particular variables to appear in
special sections, for example to map to special hardware. The
`section' attribute specifies that a variable (or function) lives
in a particular section. For example, this small program uses
several specific section names:
struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
int init_data __attribute__ ((section ("INITDATA"))) = 0;
main()
{
/* Initialize stack pointer */
init_sp (stack + sizeof (stack));
/* Initialize initialized data */
memcpy (&init_data, &data, &edata - &data);
/* Turn on the serial ports */
init_duart (&a);
init_duart (&b);
}
Use the `section' attribute with an _initialized_ definition of a
_global_ variable, as shown in the example. GCC issues a warning
and otherwise ignores the `section' attribute in uninitialized
variable declarations.
You may only use the `section' attribute with a fully initialized
global definition because of the way linkers work. The linker
requires each object be defined once, with the exception that
uninitialized variables tentatively go in the `common' (or `bss')
section and can be multiply "defined". You can force a variable
to be initialized with the `-fno-common' flag or the `nocommon'
attribute.
Some file formats do not support arbitrary sections so the
`section' attribute is not available on all platforms. If you
need to map the entire contents of a module to a particular
section, consider using the facilities of the linker instead.
簡單來說是指示gcc把標記的數據或者函數放到指定sector。
linux中把一些啟動及初始化時候用的數據用__init標識,然後在適當的時候把它們釋放,回收內存。
說到這個__init,就不能不說module_init,subsys_initcall。
在init.h中我們能夠找到 #define subsys_initcall(fn) __define_initcall("4",fn,4)
又是一個宏定義,簡直是無極中的圓環套圓環之城阿。
file:/include/linux/init.h
100 /* initcalls are now grouped by functionality into separate
101 * subsections. Ordering inside the subsections is determined
102 * by link order.
103 * For backwards compatibility, initcall() puts the call in
104 * the device init subsection.
105 *
106 * The `id' arg to __define_initcall() is needed so that multiple initcalls
107 * can point at the same handler without causing duplicate-symbol build errors.
108 */
109
110 #define __define_initcall(level,fn,id) \
111 static initcall_t __initcall_##fn##id __attribute_used__ \
112 __attribute__((__section__(".initcall" level ".init"))) = fn
subsys_initcall(usb_init)轉換後就變成了 static initcall_t __initcall_usbinit4 __attribute_used__ \
__attribute__((__section__(".initcall 4.init"))) = usb_init
就是把usb_init的函數入口指針存放在.initcall4.init中。
file:/include/asm-generic/vmlinux.lds.h
239 #define INITCALLS \
240 *(.initcall0.init) \
241 *(.initcall0s.init) \
242 *(.initcall1.init) \
243 *(.initcall1s.init) \
244 *(.initcall2.init) \
245 *(.initcall2s.init) \
246 *(.initcall3.init) \
247 *(.initcall3s.init) \
248 *(.initcall4.init) \
249 *(.initcall4s.init) \
250 *(.initcall5.init) \
251 *(.initcall5s.init) \
252 *(.initcallrootfs.init) \
253 *(.initcall6.init) \
254 *(.initcall6s.init) \
255 *(.initcall7.init) \
256 *(.initcall7s.init)
file:/arch/kernel/vmlinux_32.lds.S
144 .initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
145 __initcall_start = .;
146 INITCALLS
147 __initcall_end = .;
148 }
展開
.initcall.init : AT(ADDR(.initcall.init) - LOAD_OFFSET) {
__initcall_start = .;
*(.initcall0.init) \
*(.initcall0s.init) \
*(.initcall1.init) \
*(.initcall1s.init) \
*(.initcall2.init) \
*(.initcall2s.init) \
*(.initcall3.init) \
*(.initcall3s.init) \
*(.initcall4.init) \
*(.initcall4s.init) \
*(.initcall5.init) \
*(.initcall5s.init) \
*(.initcallrootfs.init) \
*(.initcall6.init) \
*(.initcall6s.init) \
*(.initcall7.init) \
*(.initcall7s.init)
__initcall_end = .;
}
那麼系統是如何執行這些函數呢?
此話就長了阿~ 話說盤古開天芙蓉姐姐補天后我們來到了main.c這個linux中舉足輕重的文件
進入start_kernel
start_kernel -->rest_init() -->kernel_init() --> do_basic_setup() -->do_initcalls()
這個do_initcalls()就是調用這些函數的地方。
file:/init/main.c
662 static void __init do_initcalls(void)
663 {
664 initcall_t *call;
665 int count = preempt_count();
666
667 for (call = __initcall_start; call < __initcall_end; call++) {
668 ktime_t t0, t1, delta;
669 char *msg = NULL;
670 char msgbuf[40];
671 int result;
672
673 if (initcall_debug) {
674 printk("Calling initcall 0x%p", *call);
675 print_fn_descriptor_symbol(": %s()",
676 (unsigned long) *call);
677 printk("\n");
678 t0 = ktime_get();
679 }
680
681 result = (*call)();
682
683 if (initcall_debug) {
684 t1 = ktime_get();
685 delta = ktime_sub(t1, t0);
686
687 printk("initcall 0x%p", *call);
688 print_fn_descriptor_symbol(": %s()",
689 (unsigned long) *call);
690 printk(" returned %d.\n", result);
691
692 printk("initcall 0x%p ran for %Ld msecs: ",
693 *call, (unsigned long long)delta.tv64 >> 20);
694 print_fn_descriptor_symbol("%s()\n",
695 (unsigned long) *call);
696 }
697
698 if (result && result != -ENODEV && initcall_debug) {
699 sprintf(msgbuf, "error code %d", result);
700 msg = msgbuf;
701 }
702 if (preempt_count() != count) {
703 msg = "preemption imbalance";
704 preempt_count() = count;
705 }
706 if (irqs_disabled()) {
707 msg = "disabled interrupts";
708 local_irq_enable();
709 }
710 if (msg) {
711 printk(KERN_WARNING "initcall at 0x%p", *call);
712 print_fn_descriptor_symbol(": %s()",
713 (unsigned long) *call);
714 printk(": returned with %s\n", msg);
715 }
716 }
717
718 /* Make sure there is no pending stuff from the initcall sequence */
719 flush_scheduled_work();
720 }
◎grep -- print lines matching a pattern (將符合樣式的該行列出)
◎語法: grep [options] PATTERN [FILE...]
grep用以在file內文中比對相對應的部分,或是當沒有指定檔案時,
由標準輸入中去比對。 在預設的情況下,grep會將符合樣式的那一行列出。 此外,還有兩個程式是grep的變化型,egrep及fgrep。其中egrep就等同於grep -E ,fgrep等同於grep -F 。
◎參數
1. -A NUM,--after-context=NUM
除了列出符合行之外,並且列出後NUM行。
ex: http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;grep -A 1 panda file
(從file中搜尋有panda樣式的行,並顯示該行的後1行)
2. -a或--text
grep原本是搜尋文字檔,若拿二進位的檔案作為搜尋的目標,
則會顯示如下的訊息: Binary file 二進位檔名 matches 然後結束。
若加上-a參數則可將二進位檔案視為文字檔案搜尋,
相當於--binary-files=text這個參數。
ex: (從二進位檔案mv中去搜尋panda樣式)
(錯誤!!!)
http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;grep panda mv
Binary file mv matches
(這表示此檔案有match之處,詳見--binary-files=TYPE )
http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;
(正確!!!)
http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;grep -a panda mv
3. -B NUM,--before-context=NUM
與 -A NUM 相對,但這此參數是顯示除符合行之外
並顯示在它之前的NUM行。
ex: (從file中搜尋有panda樣式的行,並顯示該行的前1行)
http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;grep -B 1 panda file
4. -C [NUM], -NUM, --context[=NUM]
列出符合行之外並列出上下各NUM行,預設值是2。
ex: (列出file中除包含panda樣式的行外並列出其上下2行)
(若要改變預設值,直接改變NUM即可)
http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;grep -C[NUM] panda file
5. -b, --byte-offset
列出樣式之前的內文總共有多少byte ..
ex: http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;grep -b panda file
顯示結果類似於:
0:panda
66:pandahuang
123:panda03
6. --binary-files=TYPE
此參數TYPE預設為binary(二進位),若以普通方式搜尋,只有2種結果:
1.若有符合的地方:顯示Binary file 二進位檔名 matches
2.若沒有符合的地方:什麼都沒有顯示。
若TYPE為without-match,遇到此參數,
grep會認為此二進位檔案沒有包含任何搜尋樣式,與-I 參數相同。
若TPYE為text, grep會將此二進位檔視為text檔案,與-a 參數相同。
Warning: --binary-files=text 若輸出為終端機,可能會產生一些不必要的輸出。
7. -c, --count
不顯示符合樣式行,只顯示符合的總行數。
若再加上-v,--invert-match,參數顯示不符合的總行數。
8. -d ACTION, --directories=ACTION
若輸入的檔案是一個資料夾,使用ACTION去處理這個資料夾。
預設ACTION是read(讀取),也就是說此資料夾會被視為一般的檔案;
若ACTION是skip(略過),資料夾會被grep略過:
若ACTION是recurse(遞),grep會去讀取資料夾下所有的檔案,
此相當於-r 參數。
9. -E, --extended-regexp
採用規則表示式去解釋樣式。
10. -e PATTERN, --regexp=PATTERN
把樣式做為一個partern,通常用在避免partern用-開始。
11. -f FILE, --file=FILE
事先將要搜尋的樣式寫入到一個檔案,一行一個樣式。
然後採用檔案搜尋。
空的檔案表示沒有要搜尋的樣式,因此也就不會有任何符合。
ex: (newfile為搜尋樣式檔)
$grep -f newfile file
12. -G, --basic-regexp
將樣式視為基本的規則表示式解釋。(此為預設)
13. -H, --with-filename
在每個符合樣式行前加上符合的檔案名稱,若有路徑會顯示路徑。
ex: (在file與testfile中搜尋panda樣式)
$grep -H panda file ./testfile
file:panda
./testfile:panda
http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;
14. -h, --no-filename
與-H參數相類似,但在輸出時不顯示路徑。
15. --help
產生簡短的help訊息。
16. -I
grep會強制認為此二進位檔案沒有包含任何搜尋樣式,
與--binary-files=without-match參數相同。
ex: http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;grep -I panda mv
17. -i, --ignore-case
忽略大小寫,包含要搜尋的樣式及被搜尋的檔案。
ex: http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;grep -i panda mv
18. -L, --files-without-match
不顯示平常一般的輸出結果,反而顯示出沒有符合的檔案名稱。
19. -l, --files-with-matches
不顯示平常一般的輸出結果,只顯示符合的檔案名稱。
20. --mmap
如果可能,使用mmap系統呼叫去讀取輸入,而不是預設的read系統呼叫。
在某些狀況,--mmap 能產生較好的效能。 然而,--mmap
如果運作中檔案縮短,或I/O 錯誤發生時,
可能造成未定義的行為(包含core dump),。
21. -n, --line-number
在顯示行前,標上行號。
ex: http://www.haoxiai.net/caozuoxitong/unixcaozuoxitong/89229.html
XXnbsp;grep -n panda file
顯示結果相似於下:
行號:符合行的內容
22. -q, --quiet, --silent
不顯示任何的一般輸出。請參閱-s或--no-messages
23. -r, --recursive
遞地,讀取每個資料夾下的所有檔案,此相當於 -d recsuse 參數。
24. -s, --no-messages
不顯示關於不存在或無法讀取的錯誤訊息。
小: 不像GNU grep,傳統的grep不符合POSIX.2協定,
因為缺乏-q參數,且他的-s 參數表現像GNU grep的 -q 參數。
Shell Script傾向將傳統的grep移植,避開-q及-s參數,
且將輸出限制到/dev/null。
POSIX: 定義UNIX及UNIX-like系統需要提供的功能。
25. -V, --version
顯示出grep的版本號到標準錯誤。
當您在回報有關grep的bugs時,grep版本號是必須要包含在內的。
26. -v, --invert-match
顯示除搜尋樣式行之外的全部。
27. -w, --word-regexp
將搜尋樣式視為一個字去搜尋,完全符合該"字"的行才會被列出。
28. -x, --line-regexp
將搜尋樣式視為一行去搜尋,完全符合該"行"的行才會被列出。
SDK: http://developer.android.com/sdk/android-2.2.html
Demo: http://www.engadget.com/2010/05/20/android-2-2-froyo-beta-hands-on-flash-10-1-wifi-hotspots-an/
一,前言
在linux的環境裡,系統訊息紀錄機制一直都是系統核心程式開發人員相當重要的參考資訊,瞭解核心訊息傳遞的機制也就成為要在Linux撰寫核心程式碼時,所需要去面對的課題,透過這篇文章筆者將為各位介紹Linux核心的訊息傳遞函式,並介紹一些簡單的方法,讓程式開發者可以更容易的控制核心訊息的導出.
這次的文章主要會使用到兩個Linux上的套件,分別為sysklogd-1.4.1.tar.gz與util-linux-2.11r.tar.gz,各位可以根據自己的需求下載不同的套件版本.
sysklogd套件主要包含兩個訊息紀錄程式,一個是klogd(Kernel Log Daemon),另一個為 syslogd(System Log Daemon),兩個工具主要的不同在於klogd是紀錄Linux 核心訊息與Linux核心模組訊息,每當核心程式呼叫printk時,就可以由這個User-Mode的klogd程式來負責把此時的核心訊息紀錄下來. 而syslogd則是負責User-Mode程式所需紀錄的系統訊息(例如紀錄在/var/log/messages的系統訊息).
Util-linux套件包含許多有用的系統工具軟體(例如,arch(取得硬體架構),ipcs(取得目前Process間通訊機制的資訊)..etc),我們主要使用的為dmesg,透過dmesg可以在User-Mode動態的查詢目前系統的核心訊息.
接下來要開始本次文章所要介紹的內容了,筆者所採用的Linux核新版本為2.4.17,在本篇文章的範圍中我將以此版本做為介紹的依據.
二,運作概念
核心紀錄訊息的Ring Buffer
Linux的核心訊息主要是透過一個環形的記憶體方式來儲存的,為了要維持這樣的記憶體空間,我們可以在 src/kernel/printk.c 當中看到以下幾個變數
(1)log_buf==>宣告Linux核心的訊息儲存空間,大小由參數LOG_BUF_LEN決定,在筆者使用的2.4.17版本中,預設的大小為32768kbytes.
(2)log_start==>指向下個要被System Call 『sys_syslog' 讀出的字元.
(使用者程式可以透過sys_syslog取得新的核心訊息)
(3)con_start==>指向下個要秀出到console的字元. 在linux的環境中printk的訊息預設是會透過console裝置輸出的,也就是使用者在電腦前使用的console裝置,如果要在console以外取得核心訊息,就必需要透過sys_syslog呼叫來取得才行.
(4)log_end==>指向log_buf訊息紀錄的尾端,當下個訊息要被加入時,就會由此加入,log_end的位址就會變成(寫入訊息的長度+1+log_end),如果這個值超過log_buf所宣告的記憶體空間,就會重新回到log_buf的啟始點寫入.
(5)logged_chars==>紀錄在上回讀取或清除核心訊息(透過呼叫sys_syslog)之後,有多少新的核心訊息尚未處理.
如下圖(一)所示,Ring Buffer的運作為在log_buf所配置的記憶體空間裡,log_start紀錄著下一筆要透過sys_syslog讀出的訊息位址,con_start紀錄著下一筆要在使用者電腦console前輸出的訊息位址,log_end紀錄著可供下一筆訊息寫入的位址,如果使用者寫入log_buf的訊息超過了log_buf所配置的記憶體空間,就會從log_buf的起始位址繼續寫入. 不過由於log_start,log_end都是會不斷的增加,所以在log_buf中定址時,會與LOG_BUF_MASK作 『&』 運算,讓log_start,log_end的值只在0-LOG_BUF_LEN的範圍內.
如圖(二),為(log_end&LOG_BUF_MASK) 超過了log_buf的記憶體空間後,又由log_buf起始點開始寫入,如此就如同一個環形,可以不斷的把訊息寫入,並且維持一個最大為LOG_BUF_LEN大小的訊息儲存空間.
圖(一),核心訊息Ring Buffer說明-1
圖(二),核心訊息Ring Buffer說明-2
klogd與dmesg運作
klogd與dmesg主要用到兩個System Call,分別為sys_syslog(80號中斷,ah=103)與sys_get_kernel_syms(80號中斷,ah=130).這兩個Linux System Call會定義在src/arch/i386/kernel/entry.S中,如下所示
.long SYMBOL_NAME(sys_syslog) /* 103 */
.long SYMBOL_NAME( sys_get_kernel_syms ) /* 130 */
其中sys_syslog被實作在src/kernel/printk.c中(sys_syslog主要是呼叫函式do_syslog來完成的),在筆者使用的2.4.17核心當中,系統紀錄訊息的空間大小是由printk.c中的變數LOG_BUF_LEN所決定的,筆者編譯的預設值為32768Kbytes,執行dmesg指令時預設傳回的大小為16392Kbytes,各位可以透過 『dmesg -s' 來指定所要取得的核心訊息大小,但每次查詢訊息時,至少都會傳回4Kbytes大小的核心訊息,只要是核心模組透過printk()所秀出的訊息,都可以透過sys_syslog來取得. 如下所示,為指令dmesg的部份程式碼,它會檢查是否查詢的訊息大小小於4Kbytes,如果小於則設為4Kbytes,大於4Kbytes則繼續執行,之後透過malloc配置記憶體,再執行klogctl(也就是透過glibc再去呼叫sys_syslog)取得核心訊息
if (bufsize < 4096)
bufsize = 4096;
buf = (char*)malloc(bufsize);
n = klogctl( cmd, buf, bufsize );
if (n < 0)
{
perror( 『klogctl' );
exit( 1 );
}
sys_get_kernel_syms則是實作在src/kernel/module.c中,主要的功能是取得所有核心模組的訊息符號表,包括所有Linux核心模組的名稱和所提供的函式名稱.取得的機製為核心透過變數module_list得到核心模組串列的首位(mod=module_list),再依序 『mod=mod->next'的取得所有的核心模組的資訊,核心模組間彼此參考的資料結構如下圖(三)所示
圖(三),核心模組的資料結構示意圖
透過module_list我們可以取的Linux環境中所有核心模組的資料串列,進而取得所有與核心模組有關的訊息. 實際上module_list所串起的為一連串的 『struct module',透過這些接續的module結構,就可以找出現在Linux核心當中有使用那一些核心模組與核心符號資訊.
有關於Linue 核心模組的相關知識,各位可以參考我過去的文章』Linux 動態載入 Module 介紹』<http://www.linuxfab.cx/indexColumnData.php?CID=84&FIRSTHIT=1>,裡面有詳細的介紹.
接下來筆者把重點放在klogd部份的說明,既然klogd主要是負則核心訊息的紀錄截取工作,我們也就有必要進一步的透過瞭解klogd來知悉獲許核心訊息的方式. Klogd啟始後,會呼叫函式InitKsyms(),讀取/boot/System.map檔案,取得目前正在運作的Linux核心所有的符號表,並且與現在系統正在運作的Linux核心版本作一個比較,如果說/boot/System.map 所屬的Linux核心版本與目前系統正在運作的核心版本並不一致的話,那就會再去其它路徑尋找System.map檔案(例如:/System.map),直到找到System.map核心版本與目前正在運作的核心一致,才會把該符號資訊檔案載入,不然的話就會產生錯誤訊息』Cannot find map file.』.
接下來透過InitMsyms()函式呼叫Linux System Call 『sys_get_kernel_syms',透過這個System Call我們可以得到所有的Module Name以及 Module所包含的所有Symbol Name.
由於 sys_get_kernel_syms 只定義了一個變數 『 struct kernel_sym *table 『,所以說當User-Mode的程式呼叫這個System Call時,首先會把table設為0來呼叫,當table為0時,它會執行第一個for迴圈
for (mod = module_list, i = 0; mod; mod = mod->next)
{
/* include the count for the module name! */
i += mod->nsyms + 1;
}
算出目前系統中所有已載入記憶體的核心模組與每個核心模組所包含的符號數目(所謂的符號指的是核心模組的函式名稱與全域變數名稱),再由User-Mode的程式根據這些核心模組的數目來配置對應的記憶體空間,例如
(rtn=sys_get_kernel_syms所傳回的核心模組符號總數)
ksym_table = (struct kernel_sym *) malloc(rtn * sizeof(struct kernel_sym));
之後再次呼叫sys_get_kernel_syms時,就會把配置好的table記憶體空間傳入,讓函式sys_get_kernel_syms把得到的核心模組名稱與所包含的核心模組符號傳給User-Mode的程式.
asmlinkage long sys_get_kernel_syms(struct kernel_sym *table)
{
struct module *mod;
int i;
struct kernel_sym ksym;
lock_kernel();
for (mod = module_list, i = 0; mod; mod = mod->next)
{
/* include the count for the module name! */
i += mod->nsyms + 1;
}
if (table == NULL)
goto out;
/* So that we don't give the user our stack content */
memset (&ksym, 0, sizeof (ksym));
for (mod = module_list, i = 0; mod; mod = mod->next)
{
struct module_symbol *msym;
unsigned int j;
if (!MOD_CAN_QUERY(mod))
continue;
/* magic: write module info as a pseudo symbol */
ksym.value = (unsigned long)mod;
ksym.name[0] = 『#』;
strncpy(ksym.name+1, mod->name, sizeof(ksym.name)-1);
ksym.name[sizeof(ksym.name)-1] = 『\0′;
//Copy those data to user mode memory
if (copy_to_user(table, &ksym, sizeof(ksym)) != 0)
goto out;
++i, ++table;
if (mod->nsyms == 0)
continue;
for (j = 0, msym = mod->syms; j < mod->nsyms; ++j, ++msym)
{
ksym.value = msym->value;
strncpy(ksym.name, msym->name, sizeof(ksym.name));
ksym.name[sizeof(ksym.name)-1] = 『\0′;
//Copy those data to user mode memory
if (copy_to_user(table, &ksym, sizeof(ksym)) != 0)
goto out;
++i, ++table;
}
}
out:
unlock_kernel();
return i;
}
之後,klogd會進入一個While(1)迴圈,如下圖(四)所示
圖(四),klogd Daemon的While迴圈運作示意圖
klogd常駐在記憶體後會透過這個while迴圈持續的執行工作,如下所述為迴圈內主要的函式說明
(1)LogKernelLine==>呼叫Linux System Call 『sys_syslog'(80號中斷,ah=103),使用參數2,為讀取最新透過printk()所秀出的訊息,如下圖(五)所示 ksyslog 被宣告為 sys_syslog,或是klogctl(是glibc提供的函式,同樣為呼叫sys_syslog)來完成.
圖(五),ksyslog的宣告
(2)LogProcLine==>在這個函式裡,會透過讀取/proc/kmsg 檔案來取得每次透過printk()所秀出的訊息,也就是說,如果各位在自己登入的終端機前執行』more /proc/kmsg' 就可以把每次核心中呼叫printk()所顯示的訊息秀出在自己的中端機前.
『/proc/kmsg'的實作程式碼是在 src/fs/proc/kmsg.c,這段程式碼實作了以下四個函式』kmsg_read,kmsg_poll,kmsg_open,kmsg_release',分別所實作的功能為
kmsg_read==>如以下程式碼,會透過呼叫do_syslog (所選擇的參數型態為2,也就是讀取新產生的核心訊息log),如果現在沒有新的核心訊息產生就會等待,直到有新的核心訊息產生,就會返回傳回新的核心訊息.
static ssize_t kmsg_read(struct file * file, char * buf, size_t count, loff_t *ppos)
{
return do_syslog(2,buf,count);
}
kmsg_poll==>如以下程式碼,會透過呼叫do_syslog (所選擇的參數型態為9,也就是傳回目前有多少未讀取的核心訊息),
static unsigned int kmsg_poll(struct file *file, poll_table * wait)
{
poll_wait(file, &log_wait, wait);
if (do_syslog(9, 0, 0))
return POLLIN | POLLRDNORM;
return 0;
}
kmsg_open==>如以下程式碼,呼叫do_syslog,參數型態為1,為開啟核心訊息的紀錄.
static int kmsg_open(struct inode * inode, struct file * file)
{
return do_syslog(1,NULL,0);
}
kmsg_release==>如以下程式碼,呼叫do_syslog,參數型態為0,為關閉核心訊息的紀錄.
static int kmsg_release(struct inode * inode, struct file * file)
{
(void) do_syslog(0,NULL,0);
return 0;
}
(3)pause==>在這個函式中,會暫停程式的執行,直到程式接收到系統所傳送的signal為止.
因此,我們可以知道klogd主要可以透過兩種方式來取得核心訊息,一個為/proc/kmsg檔案,一個為System Call sys_syslog,在運作時klogd會選擇這兩個之一作為取得訊息的機制,首先會檢查/proc/kmsg是否存在,若無就會採使用sys_syslog,當然我們也可以透過加入 『-s' 參數,執行』klogd -s'讓klogd選擇透過sys_syslog取得核心訊息,而不採用/proc/kmsg.
訊息的導出
相信寫過核心模組的人一定都遇過同樣的問題,那就是printk()所秀出的訊息總是在Console前才可以看到,如果今天是透過遠端登入Linux環境想要寫核心模組的程式,就會面對看不到訊息的困擾,所以筆者在此提供一個小技巧,可以讓使用者很輕易的把訊息導到任何一個Character Device,只要執行以下的指令
klogd -f /dev/pts/0
這行的指令意義就是說,把klogd所紀錄到的核心訊息都寫入到指定的檔案中,而每個Telnet連線或是在X Windows上終端機其實都會對應到一個pts目錄下的檔案,因此透過這樣的方式,我們可以建立一個Telnet連線或是一個X Winows終端機後,再透過指令who去查看目前所在的pts號碼,執行klogd -f 把資料導到該檔案後,就可以在指定的終端機前秀出所有Kernel 的訊息了. 如下圖(六)所示
圖(六),透過klogd把訊息轉出
同理,我們既然知道klogd也是透過/proc/kmsg的檔案來取得核心訊息的,我們也可以如法泡製,當我們連進遠端主機時,如果想要知道核心訊息時也可以執行
『more /proc/kmsg' 這樣的話,就可以在遠端針對Linux主機進行Kernel Module的修改與除錯了.
三,結語
透過這次的文章,各位可以瞭解到Linux核心訊息使用機制,相對的也可以依據自己的需求把訊息轉換到所要顯現的裝置,不過我相信不是所有人都會對這些比較細節的部份感到興趣的,撰寫本文的目的就是希望可以提供一些個人認為值得瞭解的技術,讓日後從事不同方面程式的撰寫時,可以交互應用這些資訊,整合出更好的軟體產品.
如果對這篇文章有任何疑問的話,歡迎隨時來信與我聯繫.
我的E-Mail為hlchou@mail2000.com.tw.
四,參考資料
1,Linux動態載入Module介紹,
http://linuxfab.cx/indexColumnData.php?CID=84&FIRSTHIT=1
2,util-linux,http://www.kernel.org/pub/linux/utils/util-linux
3,Assembly Programming Linux ,http://www.nk.rim.or.jp/~jun/lxasm/syscalltb.html
]]>Android libc_debug.so has a built-in function to dump all heap allocations with its backtrace, which is very useful to debug memory leaks of native processes. Below are the steps summarized during my investigation of mediaserver process:
Yaffs(Yet Another Flash File System)文件系統是專門針對NAND閃存設計的嵌入式文件系統,目前有YAFFS和YAFFS2兩個版本,兩個版本的主要區別之一在於YAFFS2能夠更好的支持大容量的NAND FLASH芯片。
Yaffs文件系統有些類似於JFFS/JFFS2文件系統,與之不同的是JFFS1/2文件系統最初是針對NOR FLASH的應用場合設計的,而NOR FLASH和NAND FLASH本質上有較大的區別,所以儘管JFFS1/2 文件系統也能應用於NAND FLASH,但由於它在內存佔用和啟動時間方面針對NOR的特性做了一些取捨,所以對NAND來說通常並不是最優的方案。
基本上NOR比較適合存儲程序代碼,其容量一般較小(比如小於32MB),價格較高,而NAND容量可達1GB以上,價格也相對便宜,適合存儲數據。一般來說,128MB以下容量NAND FLASH 芯片的一頁大小為528字節,用來存放數據,另外每一頁還有16字節的備用空間(SpareData,OOB),用來存儲ECC校驗/壞塊標誌等信息,再由若干頁組成一個塊,通常一塊為32頁16K。
與NOR相比,NAND不是完全可靠的,每塊芯片出廠時都有一定比例的壞塊存在,對數據的存取不是使用地址映射而是通過寄存器的操作,串行存取數據。
Yaffs對文件系統上的所有內容(比如正常文件,目錄,鏈接,設備文件等等)都統一當作文件來處理,每個文件都有一個頁面專門存放文件頭,文件頭保存了文件的模式、所有者id、組id、長度、文件名、Parent Object ID等信息。因為需要在一頁內放下這些內容,所以對文件名的長度,符號鏈接對象的路徑名等長度都有限制。
前面說到對於NAND FLASH上的每一頁數據,都有額外的空間用來存儲附加信息,通常NAND驅動只使用了這些空間的一部分,Yaffs正是利用了這部分空間中剩餘的部分來存儲文件系統相關的內容。以512+16B為一個PAGE的NAND FLASH芯片為例,Yaffs文件系統數據的存儲佈局如下所示:
0..511 |
數據區域 |
512..515 |
YAFFS TAG |
516 |
Data status byte |
517 |
Block status byte 壞塊標誌位 |
518..519 |
YAFFS TAG |
520..522 |
後256字節數據的ECC校驗結果 |
523..524 |
YAFFS TAG |
525..527 |
前256字節數據的ECC校驗結果 |
可以看到在這裡YAFFS一共使用了8個BYTE用來存放文件系統相關的信息(yaffs_Tags)。這8個Byte的具體使用情況按順序如下:
Bits |
Content |
20 |
ChunkID,該page在一個文件內的索引號,所以文件大小被限制在2^20 PAGE 即512Mb |
2 |
2 bits serial number |
10 |
ByteCount 該page內的有效字節數 |
18 |
ObjectID 對象ID號,用來唯一標示一個文件 |
12 |
Ecc, Yaffs_Tags本身的ECC校驗和 |
2 |
Unused |
其中Serial Number在文件系統創建時都為0,以後每次寫具有同一ObjectID和ChunkID的page的時候都加一,因為Yaffs在更新一個PAGE的時候總是在一個新的物理Page上寫入數據,再將原先的物理Page刪除,所以該serial number可以在斷電等特殊情況下,當新的page已經寫入但老的page還沒有被刪除的時候用來識別正確的Page,保證數據的正確性。
ObjectID號為18bit,所以文件的總數限制在256K即26萬個左右。
最後以上這些是針對Yaffs1而言,對於Yaffs2因為針對chunk size大於1k的NAND FLASH,在Tags各份量及總體尺寸上都做了修改,以便更快更好的處理大容量的NAND FLASH芯片。由於Tag尺寸的增大,在512+16B類型的NAND FLASH上就一個Trunk對應一個page的情況,目前就無法使用Yaffs2文件系統了。
由於文件系統的基本組織信息保存在頁面的備份空間中,因此,在文件系統加載時只需要掃瞄各個頁面的備份空間,即可建立起整個文件系統的結構,而不需要像JFFS1/2 那樣掃瞄整個介質,從而大大加快了文件系統的加載速度。
操作文件系統的第一步自然是取得SuperBlock了,Yaffs文件系統本身在NAND Flash上並不存在所謂的SuperBlock塊,完全是在文件系統mount的過程中由read_super函數填充的,不過有意思的一點是,由於物理上沒有存儲superblock塊,所以NAND Flash上的yaffs文件系統本身沒有存儲filesystem的魔數(MagicNum),在內存中superblock裡的s_magic參數也是直接賦值的,所以存儲在NAND FLASH上的任何文件系統都能被當作yaffs文件系統mount上來,只是數據都會被當作錯誤數據放在lost+found目錄中,不知道這算不算yaffs文件系統的一個bug。
通常一個具體的文件系統在VFS的Super_block結構中除了通用的數據外,還有自己專用的數據,Yaffs文件系統的專用數據是一個yaffs_DeviceStruct結構,主要用來存儲一些相關軟硬件配置信息,相關函數指針和統計信息等。
在mount過程執行read_super的過程中,Yaffs文件系統還需要將文件系統的目錄結構在內存中建立起來。由於沒有super塊,所以需要掃瞄Yaffs分區,根據從OOB中讀取出的yaffs_tags信息判斷出是文件頭page還是數據page。再根據文件頭page中的內容以及數據page中的ObjectID/ChunkID/serial Number等信息在內存中為每個文件(Object)建立一個對應的yaffs_object對象。
在yaffs_object結構中,主要包含了:
Ø 如修改時間,用戶ID,組ID等文件屬性;
Ø 用作yaffs文件系統維護用的各種標記位如髒(dirty)標記,刪除標記等等;
Ø 用作組織結構的,如指向父目錄的Parent指針,指向同級目錄中其他對象鏈表的siblings雙向鏈表頭結構
此外根據Object類型的不同(目錄,文件,鏈接),對應於某一具體類型的Object,在Yaffs_object中還有其各自專有的數據內容
Ø 普通文件:文件尺寸,用於快速查找文件數據塊的yaffs_Tnode 樹的指針等
Ø 目錄:目錄項內容雙向鏈表頭(children)
Ø 鏈接:softlink的alias,hardlink對應的ObjectID
除了對應於存儲在NAND FLASH上的object而建立起來的yaffs_object以外,在read_super執行過程中還會建立一些虛擬對象(Fake Object),這些Fake Object在NAND FLASH上沒有對應的物理實體,比如在建立文件目錄結構的最初,yaffs會建立四個虛擬目錄(Fake Directory):rootDir, unlinkedDir, deleteDir, lostNfoundDir分別用作根目錄,unlinked對象掛接的目錄,delete對象掛接的目錄,無效或零時數據塊掛接的目錄。
通過創建這些yaffs_object,yaffs文件系統就能夠將存儲在NAND FLASH上數據系統的組織起來,在內存中維護一個完整的文件系統結構。
這裡所謂移植,就是在特定的軟硬件環境裡編譯出yaffs文件系統模塊了。目前最新的yaffs版本的代碼裡主要是按照2.6內核的方式寫的Kconfig和Makefile,對於2.4內核來說,改起來也很簡單,基本上,只需要:
Ø 在內核中建立YAFFS目錄fs/yaffs,並把下載的YAFFS代碼複製到該目錄下面。
Ø 參考yaffs代碼中的Kconfig文件,按照2.4內核的風格修改你自己的Config.in文件,使得可以配置YAFFS。
Ø 修改fs/makefile,加入yaffs目錄
Ø 按照2.4內核的風格修改YAFFS目錄中的Makefile文件。
只是在配置YAFFS的時候需要注意一點,即使你的NAND FLASH是512+16B的,不需要使用YAFFS2,也需要將對2k page的NAND FLASH的支持這一項選上,否則編譯無法通過(因為部分代碼沒有用CONFIG宏包起來),不知道這是不是我下載的這個版本的個別現象,還是對Makefile還需要進一步的修改。
此外就是最好把Lets Yaffs do its own ECC選上,理由後面會說,其他選項就無所謂了,主要是對性能的調整,看著選吧,按推薦配置好了,比如Turn off debug chunk erase check,這一項,我試驗的結果選上後平均可以提高20-30%左右的擦寫速度。
Yaffs源代碼包的utils目錄下包含了mkyaffsimage/mkyaffs2image的代碼,簡單的修改一下Makefile裡的內核路徑就能編譯出mkyaffsimage/mkyaffs2image工具。
運行mkyaffsimage dir imagename可以製作出yaffs1文件系統的鏡像。
但是,需要注意的是,製作出來的yaffs image文件與通常的文件系統的image文件不同,因為在image文件裡除了以512字節為單位的一個page的data數據外,同時緊跟在後還包括了16字節為單位的NAND備份數據區(OOB)的數據。所以實際上是以528個字節為單位的。就是因為包含了這額外的16字節/page的數據,所以基本上常規辦法如dd,或者通常的下載其它類型image的工具就無法正常下載yaffs image了,需要修改你所使用的下載工具的代碼,使得它能將yaffs image中的這些額外數據也寫入NAND FLASH OOB中。
這裡還有一點需要注意的是,通過mkyaffsimage製做出來的image其OOB中也包含它自己計算的ECC校驗數據,其校驗算法有可能和MTD NAND驅動的校驗算法不同,如果在內核中由MTD來處理ECC,會造成MTD認為所有的page都校驗錯誤。所以,這也是我前面說最好把Lets Yaffs do its own ECC選上的原因,同時,要把MTD NAND驅動中的ECC校驗關閉。
如果不考慮產線批量下載的話,也可以通過mount拷貝的方式準備yaffs文件系統。用flash_eraseall將NAND FLASH分區擦除,然後做為yaffs分區直接mount上來,將文件系統的內容拷貝上去就可以了。這可能是在真正的NAND FLASH上試驗yaffs文件系統最簡單的方式了。
沒有相應的NAND FLASH設備包含兩種情況:
Ø 硬件上沒有NAND FLASH,開發板上沒有或者想在主機環境中測試yaffs文件系統
Ø 沒有合適的page size的NAND FLASH芯片,比如板上NAND FLASH芯片為512+16的格式,但是想要試驗Yaffs2文件系統。
Yaffs提供了兩種用來在這種情況下測試yaffs文件系統的途徑。
2.2.3.1 Nandemul
Yaffs source包裡包含了mtdemul目錄,Yaffs2中該目錄下的文件主要是Nandemul2k.c用來模擬2K page size的NAND FLASH。在Yaffs中則是Nandemul.c用來模擬512字節page size的NAND FLASH。
稍微修改一下Makeflie將編譯出來的模塊插入內核,將在/dev/mtd /dev/mtdblock目錄下創建一個新的MTD設備。然後就可以將該設備當作一個物理的MTD NAND設備分區進行相關的操作,可以在上面創建yaffs文件系統,mount umount等等。這種方法不僅適用於yaffs文件系統,同樣也適用於其它可用於NAND設備的文件系統。
2.2.3.2 Yaffsram
根據yaffs 官方文檔的描述,通過mount –t yaffsram none /mountpoint 可以在內存中建立一個yaffs分區,這有些類似於ramfs。不過,在試驗最新版本的Yaffs2文件系統時,該功能並不可用,只有Yaffs1文件系統的代碼包裡包含了相關的代碼。
]]>
可直接使用 man 指令去查 api/sys call,在ubuntu 下可先安裝下列兩個pkg:
$ sudo apt-get install manpages-dev
$ sudo apt-get install glibc-doc
範例:
$ man malloc
$ man 2 reboot
由於同樣的keyword 可能有不同用途,所以直接打 man reboot 查到的是一般的指令,而不是 sys call 的使用方式。
man 提供以下類型,指定使用代號即可查相對應內容:
代號 | 代表內容 |
1 | 使用者在shell環境中可以操作的指令或可執行檔 |
2 | 系統核心可呼叫的函數與工具等 |
3 | 一些常用的函數(function)與函式庫(library),大部分為C的函式庫(libc) |
4 | 裝置檔案的說明,通常在/dev下的檔案 |
5 | 設定檔或者是某些檔案的格式 |
6 | 遊戲(games) |
7 | 慣例與協定等,例如Linux檔案系統、網路協定、ASCII code等等的說明 |
8 | 系統管理員可用的管理指令 |
9 | 跟kernel有關的文件 |
Kernel api
kernel 沒有 magpage,直接參考 kernel src tree,或是 Kernel.org 有 maintain 一份 kernel api:
]]>利用capability特徵加強Linux系統安全
摘要
傳統UNIX系統的訪問控制模型非常簡單--普通用戶對超級用戶。在這種模型中,一個進程或者帳戶要麼只有很小的許可權,要麼具有全部的系統許可權。顯然,這樣對系統的安全沒有什麼好處。從Linux-2.1內核開始,引入了能力(capability)的概念,實現了更細粒度的訪問控制。(2002-07-15 09:53:08)
By nixe0n
1.簡介
UNIX是一種安全作業系統,它給普通用戶儘可能低的許可權,而把全部的系統許可權賦予一個單一的帳戶--root。root帳戶用來管理系統、安裝軟體、管理帳戶、運行某些服務、安裝/卸載檔系統、管理用戶、安裝軟體等。另外,普通用戶的很多操作也需要root許可權,這通過setuid實現。
這種依賴單一帳戶執行特權操作的方式加大了系統的面臨風險,而需要root許可權的程式可能只是為了一個單一的操作,例如:綁定到特權埠、打開一個只有root許可權可以訪問的檔。某些程式可能有安全漏洞,而如果程式不是以root的許可權運行,其存在的漏洞就不可能對系統造成什麼威脅。
從2.1版開始,內核開發人員在Linux內核中加入了能力(capability)的概念。其目標是消除需要執行某些操作的程式對root帳戶的依賴。從2.2版本的內核開始,這些代基本可以使用了,雖然還存在一些問題,但是方向是正確的。
2.Linux內核能力詳解
傳統UNIX的信任狀模型非常簡單,就是「超級用戶對普通用戶」模型。在這種模型中,一個進程要麼什麼都能做,要麼幾乎什麼也不能做,這取決於進程的UID。如果一個進程需要執行綁定到私有埠、載入/卸載內核模組以及管理檔系統等操作時,就需要完全的root許可權。很顯然這樣做對系統安全存在很大的威脅。UNIX系統中的SUID問題就是由這種信任狀模型造成的。例如,一個普通用戶需要使用ping命令。這是一個SUID命令,會以root的許可權運行。而實際上這個程式只是需要RAW套接字建立必要ICMP資料包,除此之外的其他root許可權對這個程式都是沒有必要的。如果程式編寫不好,就可能被攻擊者利用,獲得系統的控制權。
使用能力(capability)可以減小這種風險。系統管理員為了系統的安全可以剝奪root用戶的能力,這樣即使root用戶也將無法進行某些操作。而這個過程又是不可逆的,也就是說如果一種能力被刪除,除非重新啟動系統,否則即使root用戶也無法重新添加被刪除的能力。
2.1.能力的概念
Linux內核中使用的能力(capability)概念非常容易被混淆。電腦科學中定義了很多種能力(capability)。能力就是一個進程能夠對某個物件進行的操作,它標誌物件以及允許在這個物件上進行的操作。檔描述符就是一種能力,你使用open系統調用請求獲得讀或者寫的許可權,如果open系統調用成功,系統的內核就會建立一個檔描述符。然後,如果收到讀或者寫的請求,內核就使用這個檔描述符作為一個資料結構的索引,檢索相關的操作是否允許。這是一種檢查許可權的有效方式,在執行open系統調用是,內核一次性建立必要的資料結構,然後的讀寫等操作檢查只需要在資料結構中梭梭即可。對能力的操作包括:複製能力、進程間的遷移能力、修改一個能力以及撤銷一個能力等。修改一個能力類似與把一個可以讀寫的檔描述符改為唯讀。目前,各種系統對能力的應用程度並不相同。
POSIX 1003.1e中也提出了一種能力定義,通常稱為POSIX能力(POSIX capabilities),Linux中的定義不大一樣。內核使用這些能力分割root的許可權,因為傳統*NIX系統中root的許可權過於強大了。
2.2.Linux是如何使用POSIX capabilities代替傳統的信任狀模型的
每個進程有三個和能力有關的點陣圖:inheritable(I)、permitted(P)和effective(E),對應進程描述符task_struct(include/linux/sched.h)裡面的cap_effective, cap_inheritable, cap_permitted。每種能力由一位表示,1表示具有某種能力,0表示沒有。當一個進程要進行某個特權操作時,作業系統會檢查cap_effective的對應位是否有效,而不再是檢查進程的有效UID是否為0。例如,如果一個進程要設置系統的時鐘,Linux的內核就會檢查cap_effective的CAP_SYS_TIME位(第25位)是否有效,
cap_permitted表示進程能夠使用的能力。在cap_permitted中可以包含cap_effective中沒有的能力,這些能力是被進程自己臨時放棄的,也可以說cap_effective是cap_permitted的一個子集。進程放棄沒有必要的能力對於提高安全性大有助益。例如,ping只需要CAP_NET_RAW,如果它放棄除這個能力之外的其他能力,即使存在安全缺陷,也不會對系統造成太大的損害。cap_inheritable表示能夠被當前進程執行的程式繼承的能力。
3.Linux支援的能力
Linux實現了7個POSIX 1003.1e規定的能力,還有21個(截止到2.4.7-10版本的內核)Linux所特有的,這些能力在/usr/src/linux/include/linux/capability.h檔中定義。其細節如下:
能力名 數字 描述
CAP_CHOWN 0 允許改變檔的所有權
CAP_DAC_OVERRIDE 1 忽略對檔的所有DAC訪問限制
CAP_DAC_READ_SEARCH 2 忽略所有對讀、搜索操作的限制
CAP_FOWNER 3 如果檔屬於進程的UID,就取消對檔的限制
CAP_FSETID 4 允許設置setuid位
CAP_KILL 5 允許對不屬於自己的進程發送信號
CAP_SETGID 6 允許改變組ID
CAP_SETUID 7 允許改變用戶ID
CAP_SETPCAP 8 允許向其他進程轉移能力以及刪除其他進程的任意能力
CAP_LINUX_IMMUTABLE 9 允許修改檔的不可修改(IMMUTABLE)和只添加(APPEND-ONLY)屬性
CAP_NET_BIND_SERVICE 10 允許綁定到小於1024的埠
CAP_NET_BROADCAST 11 允許網路廣播和多播訪問
CAP_NET_ADMIN 12 允許執行網路管理任務:介面、防火牆和路由等,詳情請參考/usr/src/linux/include/linux/capability.h文件
CAP_NET_RAW 13 允許使用原始(raw)套接字
CAP_IPC_LOCK 14 允許鎖定共用記憶體片段
CAP_IPC_OWNER 15 忽略IPC所有權檢查
CAP_SYS_MODULE 16 插入和刪除內核模組
CAP_SYS_RAWIO 17 允許對ioperm/iopl的訪問
CAP_SYS_CHROOT 18 允許使用chroot()系統調用
CAP_SYS_PTRACE 19 允許跟蹤任何進程
CAP_SYS_PACCT 20 允許配置進程記帳(process accounting)
CAP_SYS_ADMIN 21 允許執行系統管理任務:載入/卸載檔系統、設置磁片配額、開/關交換設備和檔等。詳情請參考/usr/src/linux/include/linux/capability.h文件。
CAP_SYS_BOOT 22 允許重新啟動系統
CAP_SYS_NICE 23 允許提升優先順序,設置其他進程的優先順序
CAP_SYS_RESOURCE 24 忽略資源限制
CAP_SYS_TIME 25 允許改變系統時鐘
CAP_SYS_TTY_CONFIG 26 允許配置TTY設備
CAP_MKNOD 27 允許使用mknod()系統調用
CAP_LEASE 28 Allow taking of leases on files
4.能力邊界集
Linux2.2內核提供了對能力的基本支援。但是在引入了能力之後遇到了一些困難,雖然2.2版本的內核能夠理解能力,但是缺乏一個系統和用戶之間的介面。除此之外,還存在其他的一些問題。從2.2.11版本開始,這種情況發生了很大的改觀,在這個版本中引入了能力邊界集(capability bounding set)的概念,解決了和系統和用戶之間的介面問題。能力邊界集(capability bounding set)是系統中所有進程允許保留的能力。如果在能力邊界集中不存在某個能力,那麼系統中的所有進程都沒有這個能力,即使以超級用戶許可權執行的進程也一樣。
能力邊界集通過sysctl命令導出,用戶可以在/proc/sys/kernel/cap-bound中看到系統保留的能力。在默認情況下,能力邊界集所有的位都是打開的。
root用戶可以向能力邊界集中寫入新的值來修改系統保留的能力。但是要注意,root用戶能夠從能力邊界集中刪除能力,卻不能再恢復被刪除的能力,只有init進程能夠添加能力。通常,一個能力如果從能力邊界集中被刪除,只有系統重新啟動才能恢復。
刪除系統中多餘的能力對提高系統的安全性是很有好處的。假設你有一台重要的伺服器,比較擔心可載入內核模組的安全性。而你又不想完全禁止在系統中使用可載入內核模組或者一些設備的驅動就是一些內核模組。在這種情況下,最好使系統在啟動時載入所有的模組,然後禁止載入/卸載任何內核模組。在Linux系統中,載入/卸載內核模組是由CAP_SYS_MODULE能力控制的。如果把CAP_SYS_MODULE從能力邊界集中刪除,系統將不再允許載入/卸載任何的內核模組。
CAP_SYS_MODULE能力的值是16,因此我們使用下面的命令就可以把它從能力邊界集中刪除:
echo 0xFFFEFFFF >/proc/sys/kernel/cap-bound
5.lcap
雖然我們可以直接修改/proc/sys/kernel/cap-bound來刪除系統的某中能力,但是這樣畢竟非常的不方便。有一個程式lcap可以幫助我們更方便的從系統中刪除指定的能力。它可以從http://home.netcom.com/~spoon/lcap/下載。編譯之後就可以直接使用。如果不帶參數,lcap可以列出系統當前支援的各種能力:
[root@nixe0n lcap-0.0.6]# ./lcap
Current capabilities: 0xFFFFFEFF
0) *CAP_CHOWN 1) *CAP_DAC_OVERRIDE
2) *CAP_DAC_READ_SEARCH 3) *CAP_FOWNER
4) *CAP_FSETID 5) *CAP_KILL
6) *CAP_SETGID 7) *CAP_SETUID
8) CAP_SETPCAP 9) *CAP_LINUX_IMMUTABLE
10) *CAP_NET_BIND_SERVICE 11) *CAP_NET_BROADCAST
12) *CAP_NET_ADMIN 13) *CAP_NET_RAW
14) *CAP_IPC_LOCK 15) *CAP_IPC_OWNER
16) *CAP_SYS_MODULE 17) *CAP_SYS_RAWIO
18) *CAP_SYS_CHROOT 19) *CAP_SYS_PTRACE
20) *CAP_SYS_PACCT 21) *CAP_SYS_ADMIN
22) *CAP_SYS_BOOT 23) *CAP_SYS_NICE
24) *CAP_SYS_RESOURCE 25) *CAP_SYS_TIME
26) *CAP_SYS_TTY_CONFIG
* = Capabilities currently allowed
如果我們需要刪除某個能力,直接把能力名作為參數就可以,例如我們要刪除載入/卸載內核模組的能力:
[root@nixe0n lcap-0.0.6]# ./lcap CAP_SYS_MODULE
[root@nixe0n lcap-0.0.6]# ./lcap
Current capabilities: 0xFFFBFEFF
0) *CAP_CHOWN 1) *CAP_DAC_OVERRIDE
2) *CAP_DAC_READ_SEARCH 3) *CAP_FOWNER
4) *CAP_FSETID 5) *CAP_KILL
6) *CAP_SETGID 7) *CAP_SETUID
8) CAP_SETPCAP 9) *CAP_LINUX_IMMUTABLE
10) *CAP_NET_BIND_SERVICE 11) *CAP_NET_BROADCAST
12) *CAP_NET_ADMIN 13) *CAP_NET_RAW
14) *CAP_IPC_LOCK 15) *CAP_IPC_OWNER
16) CAP_SYS_MODULE 17) *CAP_SYS_RAWIO
18) *CAP_SYS_CHROOT 19) *CAP_SYS_PTRACE
20) *CAP_SYS_PACCT 21) *CAP_SYS_ADMIN
22) *CAP_SYS_BOOT 23) *CAP_SYS_NICE
24) *CAP_SYS_RESOURCE 25) *CAP_SYS_TIME
26) *CAP_SYS_TTY_CONFIG
* = Capabilities currently allowed
6.能力邊界集的安全問題
能力邊界集為系統和管理員之間提供了一個便利的交互介面,但是它存在一些的脆弱性。Patrick Reynolds在提交到BugTraq的一個郵件裡詳細分析了這種脆弱性。對能力邊界集的最大威脅就是能夠被讀/寫的/dev/mem設備。在內核記憶體區中,/proc/sys/kernel/cap-bound直接影射到cap_bset變數中。如果/dev/mem可以寫,攻擊者就能夠直接修改記憶體重置cap_bset變數。從而能夠越過能力邊界集打開所有的能力。使用以下命令就可以獲得cap_bset變數的位址:
$ grep cap_bset System.map
c01f0cd5 ? __kstrtab_cap_bset
c01f7340 ? __ksymtab_cap_bset
c01fb2ac D cap_bset
從結果可以看出,cap_bset位於c01fb2ac。攻擊者獲得了/dev/mem的寫許可權,只要寫入0xffffffff就能夠重新打開所有的能力。
因此,為了維護能力邊界集的安全,你應該放棄系統的CAP_SYS_RAWIO能力。這樣會造成X系統和其他一些需要訪問/dev/mem或I/O埠的程式無法運行,不過對於伺服器來說,這是值得的。除了關閉CAP_SYS_RAWIO,還應該放棄CAP_SYS_MODULE能力。
7.侷限
雖然利用能力可已經以有效地保護系統的安全,但是由於檔系統的制約,Linux的能力控制還不是很完善。我們除了可以使用lcap從總體上放棄一些能力之外,伺服器軟體程式師也應該主動放棄進程的一些多餘的能力。例如,xntpd程式可以通過以下的步驟放棄沒有必要的能力,以加強安全性:
• 以完整的root許可權啟動
•
• 綁定到ntp埠
•
• 除了CAP_SYS_TIME能力之外,放棄其他的能力
•
• 放棄root許可權
•
• 以普通管理帳戶的身份進行正常的操作
•
但是,並不是所有的程式師能夠注意到這個問題,如果能夠直接使用chmod和chattr命令限制程式的能力將給為方便。例如:
[root@localhost /root]# chattr +CAP_BIND xntpd
目前,由於檔系統的制約,還無法實現。
8.結論
在本文,我們討論了Linux的能力,並說明了如何使用相關的工具加強系統的安全性。但是,能力還守制於檔系統的擴展,並不是非常完善。
endian指的是當物理上的最小單元比邏輯上的最小單元小時,邏輯單元對映到物理單元的排布關係。
實際的例子
如果你在文件上看到一個雙字組的data,Ex: long MyData=0x12345678,要寫到從0x0000開始的記憶體位址時。
比較的結果就是這樣:
big-endian | little-endian | |
0x0000 | 0x12 | 0x78 |
0x0001 | 0x34 | 0x56 |
0x0002 | 0x56 | 0x34 |
0x0003 | 0x78 | 0x12 |
這有什麼差別呢?
以目前常見的CPU為例:
另外,以我目前在SID測試的程式testendian.c為例,我必須在以下的三個地方都必須指定Endian,否則即使Build時可以通過,但在計算時就可能會出現問題,因為放入記憶體內的資料可能是反過來的。
testendian.c
#include <stdio.h>
typedef union { long l; unsigned char c[4]; } EndianTest;
int main(int argc, char* argv[]) {
EndianTest a;
a.l=0x12345678;
int i=0;
if(a.c[0]==0x78 && a.c[1]==0x56 && a.c[2]==0x34 && a.c[3]==0x12) {
printf("This system is 'Little Endian'.\n"); }
else if(a.c[0]==0x12 && a.c[1]==0x34 && a.c[2]==0x56 && a.c[3]==0x78) {
printf("This system is 'Big Endian'.\n"); }
else {printf("This system is 'Unknown Endian'.\n"); }
printf("for a long variable value is 0x%lX\n",a.l);
printf("and its storage order in memory :\n");
for(i=0;i<4;i++) printf("%p : 0x%02X\n",&a.c[i],a.c[i]);
// getchar(); // wait for a key ..。
return 0;
}
]]>
GNU C的一大特色(卻不被初學者所知)就是__attribute__機制。__attribute__可以設置函數屬性(Function Attribute)、變量屬性(Variable Attribute)和類型屬性(Type Attribute)。
__attribute__書寫特徵是:__attribute__前後都有兩個下劃線,並切後面會緊跟一對原括弧,括弧裡面是相應的__attribute__參數。
__attribute__語法格式為:
__attribute__ ((attribute-list))
其位置約束為:
放於聲明的尾部「;」之前。
函數屬性(Function Attribute)
函數屬性可以幫助開發者把一些特性添加到函數聲明中,從而可以使編譯器在錯誤檢查方面的功能更強大。__attribute__機制也很容易同非GNU應用程序做到兼容之功效。
GNU CC需要使用 –Wall編譯器來擊活該功能,這是控制警告信息的一個很好的方式。下面介紹幾個常見的屬性參數。
__attribute__ format
該__attribute__屬性可以給被聲明的函數加上類似printf或者scanf的特徵,它可以使編譯器檢查函數聲明和函數實際調用參數之間的格式化字符串是否匹配。該功能十分有用,尤其是處理一些很難發現的bug。
format的語法格式為:
format (archetype, string-index, first-to-check)
format屬性告訴編譯器,按照printf, scanf, strftime或strfmon的參數表格式規則對該函數的參數進行檢查。「archetype」指定是哪種風格;「string-index」指定傳入函數的第幾個參數是格式化字符串;「first-to-check」指定從函數的第幾個參數開始按上述規則進行檢查。
具體使用格式如下:
__attribute__((format(printf,m,n)))
__attribute__((format(scanf,m,n)))
其中參數m與n的含義為:
m:第幾個參數為格式化字符串(format string);
n:參數集合中的第一個,即參數「…」裡的第一個參數在函數參數總數排在第幾,注意,有時函數參數里還有「隱身」的呢,後面會提到;
在使用上,__attribute__((format(printf,m,n)))是常用的,而另一種卻很少見到。下面舉例說明,其中myprint為自己定義的一個帶有可變參數的函數,其功能類似於printf:
//m=1;n=2
extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
//m=2;n=3
extern void myprint(int l,const char *format,...) __attribute__((format(printf,2,3)));
需要特別注意的是,如果myprint是一個函數的成員函數,那麼m和n的值可有點「懸乎」了,例如:
//m=3;n=4
extern void myprint(int l,const char *format,...) __attribute__((format(printf,3,4)));
其原因是,類成員函數的第一個參數實際上一個「隱身」的「this」指針。(有點C++基礎的都知道點this指針,不知道你在這裡還知道嗎?)
這裡給出測試用例:attribute.c,代碼如下:
1:
2:extern void myprint(const char *format,...) __attribute__((format(printf,1,2)));
3:
4:void test()
5:{
6: myprint("i=%d\n",6);
7: myprint("i=%s\n",6);
8: myprint("i=%s\n","abc");
9: myprint("%s,%d,%d\n",1,2);
10:}
運行$gcc –Wall –c attribute.c attribute後,輸出結果為:
attribute.c: In function `test':
attribute.c:7: warning: format argument is not a pointer (arg 2)
attribute.c:9: warning: format argument is not a pointer (arg 2)
attribute.c:9: warning: too few arguments for format
如果在attribute.c中的函數聲明去掉__attribute__((format(printf,1,2))),再重新編譯,既運行$gcc –Wall –c attribute.c attribute後,則並不會輸出任何警告信息。
注意,默認情況下,編譯器是能識別類似printf的「標準」庫函數。
__attribute__ noreturn
該屬性通知編譯器函數從不返回值,當遇到類似函數需要返回值而卻不可能運行到返回值處就已經退出來的情況,該屬性可以避免出現錯誤信息。C庫函數中的abort()和exit()的聲明格式就採用了這種格式,如下所示:
extern void exit(int) __attribute__((noreturn));
extern void abort(void) __attribute__((noreturn));
為了方便理解,大家可以參考如下的例子:
//name: noreturn.c ;測試__attribute__((noreturn))
extern void myexit();
int test(int n)
{
if ( n > 0 )
{
myexit();
/* 程序不可能到達這裡*/
}
else
return 0;
}
編譯顯示的輸出信息為:
$gcc –Wall –c noreturn.c
noreturn.c: In function `test':
noreturn.c:12: warning: control reaches end of non-void function
警告信息也很好理解,因為你定義了一個有返回值的函數test卻有可能沒有返回值,程序當然不知道怎麼辦了!
加上__attribute__((noreturn))則可以很好的處理類似這種問題。把
extern void myexit();
修改為:
extern void myexit() __attribute__((noreturn));
之後,編譯不會再出現警告信息。
__attribute__ const
該屬性只能用於帶有數值類型參數的函數上。當重複調用帶有數值參數的函數時,由於返回值是相同的,所以此時編譯器可以進行優化處理,除第一次需要運算外,其它只需要返回第一次的結果就可以了,進而可以提高效率。該屬性主要適用於沒有靜態狀態(static state)和副作用的一些函數,並且返回值僅僅依賴輸入的參數。
為了說明問題,下面舉個非常「糟糕」的例子,該例子將重複調用一個帶有相同參數值的函數,具體如下:
extern int square(int n) __attribute__((const));
...
for (i = 0; i < 100; i++ )
{
total += square(5) + i;
}
通過添加__attribute__((const))聲明,編譯器只調用了函數一次,以後只是直接得到了相同的一個返回值。
事實上,const參數不能用在帶有指針類型參數的函數中,因為該屬性不但影響函數的參數值,同樣也影響到了參數指向的數據,它可能會對代碼本身產生嚴重甚至是不可恢復的嚴重後果。
並且,帶有該屬性的函數不能有任何副作用或者是靜態的狀態,所以,類似getchar()或time()的函數是不適合使用該屬性的。
-finstrument-functions
該參數可以使程序在編譯時,在函數的入口和出口處生成instrumentation調用。恰好在函數入口之後並恰好在函數出口之前,將使用當前函數的地址和調用地址來調用下面的 profiling 函數。(在一些平台上,__builtin_return_address不能在超過當前函數範圍之外正常工作,所以調用地址信息可能對profiling函數是無效的。)
void __cyg_profile_func_enter(void *this_fn, void *call_site);
void __cyg_profile_func_exit(void *this_fn, void *call_site);
其中,第一個參數this_fn是當前函數的起始地址,可在符號表中找到;第二個參數call_site是指調用處地址。
instrumentation 也可用於在其它函數中展開的內聯函數。從概念上來說,profiling調用將指出在哪裡進入和退出內聯函數。這就意味著這種函數必須具有可尋址形式。如果函數包含內聯,而所有使用到該函數的程序都要把該內聯展開,這會額外地增加代碼長度。如果要在C 代碼中使用extern inline聲明,必須提供這種函數的可尋址形式。
可對函數指定no_instrument_function屬性,在這種情況下不會進行instrumentation操作。例如,可以在以下情況下使用no_instrument_function屬性:上面列出的profiling函數、高優先級的中斷例程以及任何不能保證profiling正常調用的函數。
no_instrument_function
如果使用了-finstrument-functions ,將在絕大多數用戶編譯的函數的入口和出口點調用profiling函數。使用該屬性,將不進行instrument操作。
constructor/destructor
若函數被設定為constructor屬性,則該函數會在main()函數執行之前被自動的執行。類似的,若函數被設定為destructor屬性,則該函數會在main()函數執行之後或者exit()被調用後被自動的執行。擁有此類屬性的函數經常隱式的用在程序的初始化數據方面。
這兩個屬性還沒有在ObjC中實現。
同時使用多個屬性
可以在同一個函數聲明裡使用多個__attribute__,並且實際應用中這種情況是十分常見的。使用方式上,你可以選擇兩個單獨的__attribute__,或者把它們寫在一起,可以參考下面的例子:
/* 把類似printf的消息傳遞給stderr 並退出 */
extern void die(const char *format, ...)
__attribute__((noreturn))
__attribute__((format(printf, 1, 2)));
或者寫成
extern void die(const char *format, ...)
__attribute__((noreturn, format(printf, 1, 2)));
如果帶有該屬性的自定義函數追加到庫的頭文件裡,那麼所以調用該函數的程序都要做相應的檢查。
和非GNU編譯器的兼容性
慶幸的是,__attribute__設計的非常巧妙,很容易作到和其它編譯器保持兼容,也就是說,如果工作在其它的非GNU編譯器上,可以很容易的忽略該屬性。即使__attribute__使用了多個參數,也可以很容易的使用一對圓括弧進行處理,例如:
/* 如果使用的是非GNU C, 那麼就忽略__attribute__ */
#ifndef __GNUC__
# define __attribute__(x) /*NOTHING*/
#endif
需要說明的是,__attribute__適用於函數的聲明而不是函數的定義。所以,當需要使用該屬性的函數時,必須在同一個文件裡進行聲明,例如:
/* 函數聲明 */
void die(const char *format, ...) __attribute__((noreturn))
__attribute__((format(printf,1,2)));
void die(const char *format, ...)
{
/* 函數定義 */
}
更多的屬性含義參考:
http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Function-Attributes.html
變量屬性(Variable Attributes)
關鍵字__attribute__也可以對變量(variable)或結構體成員(structure field)進行屬性設置。這裡給出幾個常用的參數的解釋,更多的參數可參考本文給出的連接。
在使用__attribute__參數時,你也可以在參數的前後都加上「__」(兩個下劃線),例如,使用__aligned__而不是aligned,這樣你就可以在相應的頭文件裡使用它而不用關心頭文件裡是否有重名的宏定義。
aligned (alignment)
該屬性規定變量或結構體成員的最小的對齊格式,以字節為單位。例如:
int x __attribute__ ((aligned (16))) = 0;
編譯器將以16字節(注意是byte不是bit)對齊的方式分配一個變量。也可以對結構體成員變量設置該屬性,例如,創建一個雙byte對齊的int對,可以這麼寫:
struct foo { int x[2] __attribute__ ((aligned (8))); };
如上所述,你可以手動指定對齊的格式,同樣,你也可以使用默認的對齊方式。如果aligned後面不緊跟一個指定的數字值,那麼編譯器將依據你的目標機器情況使用最大最有益的對齊方式。例如:
short array[3] __attribute__ ((aligned));
選擇針對目標機器最大的對齊方式,可以提高拷貝操作的效率。
aligned屬性使被設置的對象佔用更多的空間,相反的,使用packed可以減小對象佔用的空間。
需要注意的是,attribute屬性的效力與你的連接器也有關,如果你的連接器最大隻支持16字節對齊,那麼你此時定義32字節對齊也是無濟於事的。
packed
使用該屬性可以使得變量或者結構體成員使用最小的對齊方式,即對變量是一字節對齊,對域(field)是bit對齊。
下面的例子中,x成員變量使用了該屬性,則其值將緊放置在a的後面:
struct test{
char a;
int x[2] __attribute__ ((packed));
};
其它可選的屬性值還可以是:cleanup,common,nocommon,deprecated,mode,section,shared,tls_model,transparent_union,unused,vector_size,weak,dllimport,dlexport等,
詳細信息可參考:http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Variable-Attributes.html#Variable-Attributes
關鍵字__attribute__也可以對結構體(struct)或共用體(union)進行屬性設置。大緻有六個參數值可以被設定,即:aligned, packed, transparent_union, unused, deprecated 和 may_alias。
在使用__attribute__參數時,你也可以在參數的前後都加上「__」(兩個下劃線),例如,使用__aligned__而不是aligned,這樣,你就可以在相應的頭文件裡使用它而不用關心頭文件裡是否有重名的宏定義。
aligned (alignment)
該屬性設定一個指定大小的對齊格式(以字節為單位),例如:
struct S { short f[3]; } __attribute__ ((aligned (8)));
typedef int more_aligned_int __attribute__ ((aligned (8)));
該聲明將強制編譯器確保(盡它所能)變量類型為struct S或者more-aligned-int的變量在分配空間時採用8字節對齊方式。
如上所述,你可以手動指定對齊的格式,同樣,你也可以使用默認的對齊方式。如果aligned後面不緊跟一個指定的數字值,那麼編譯器將依據你的目標機器情況使用最大最有益的對齊方式。例如:
struct S { short f[3]; } __attribute__ ((aligned));
這裡,如果sizeof(short)的大小為2(byte),那麼,S的大小就為6。取一個2的次方值,使得該值大於等於6,則該值為8,所以編譯器將設置S類型的對齊方式為8字節。
aligned屬性使被設置的對象佔用更多的空間,相反的,使用packed可以減小對象佔用的空間。
需要注意的是,attribute屬性的效力與你的連接器也有關,如果你的連接器最大隻支持16字節對齊,那麼你此時定義32字節對齊也是無濟於事的。
packed
使用該屬性對struct或者union類型進行定義,設定其類型的每一個變量的內存約束。當用在enum類型定義時,暗示了應該使用最小完整的類型(it indicates that the smallest integral type should be used)。
下面的例子中,my-packed-struct類型的變量數組中的值將會緊緊的靠在一起,但內部的成員變量s不會被「pack」,如果希望內部的成員變量也被packed的話,my-unpacked-struct也需要使用packed進行相應的約束。
struct my_unpacked_struct
{
char c;
int i;
};
struct my_packed_struct
{
char c;
int i;
struct my_unpacked_struct s;
}__attribute__ ((__packed__));
其它屬性的含義見:http://gcc.gnu.org/onlinedocs/gcc-4.0.0/gcc/Type-Attributes.html#Type-Attributes
下面的例子中使用__attribute__屬性定義了一些結構體及其變量,並給出了輸出結果和對結果的分析。
程序代碼為:
struct p
{
int a;
char b;
char c;
}__attribute__((aligned(4))) pp;
struct q
{
int a;
char b;
struct n qn;
char c;
}__attribute__((aligned(8))) qq;
int main()
{
printf("sizeof(int)=%d,sizeof(short)=%d.sizeof(char)=%d\n",sizeof(int),sizeof(short),sizeof(char));
printf("pp=%d,qq=%d \n", sizeof(pp),sizeof(qq));
return 0;
}
輸出結果:
sizeof(int)=4,sizeof(short)=2.sizeof(char)=1
pp=8,qq=24
分析:
sizeof(pp):
sizeof(a)+ sizeof(b)+ sizeof(c)=4+1+1=6<23=8= sizeof(pp)
sizeof(qq):
sizeof(a)+ sizeof(b)=4+1=5
sizeof(qn)=8;即qn是採用8字節對齊的,所以要在a,b後面添3個空餘字節,然後才能存儲qn,
4+1+(3)+8+1=17
因為qq採用的對齊是8字節對齊,所以qq的大小必定是8的整數倍,即qq的大小是一個比17大又是8的倍數的一個最小值,由此得到
17<24+8=24= sizeof(qq)
更詳細的介紹見:http://gcc.gnu.org
下面是一些便捷的連接:GCC 4.0 Function Attributes;GCC 4.0 Variable Attributes ;GCC 4.0 Type Attributes ;GCC 3.2 Function Attributes ;GCC 3.2 Variable Attributes ;GCC 3.2 Type Attributes ;GCC 3.1 Function Attributes ;GCC 3.1 Variable Attributes
Reference:
1.有關__attribute__的相對簡單的介紹:http://www.unixwiz.net/techtips/gnu-c-attributes.html
2.__attribute__詳細介紹:http://gcc.gnu.org
$vim /etc/udev/rules.d/90-android.rules
1.
2.
3.
$sudo restart udev
It works now.
]]>
Android Binder,基於OpenBinder框架的一個驅動,用於提供Android平台的process間通訊(IPC,inter-process communication)。
源代碼位於drivers/staging/android/binder.c
Android電源管理(PM),一個基於標準Linux電源管理系統的輕量級的Android電源管理驅動,針對嵌入式設備做了很多最佳化。
源代碼位於kernel/power/earlysuspend.c
kernel/power/consoleearlysuspend.c
kernel/power/fbearlysuspend.c
kernel/power/wakelock.c
kernel/power/userwakelock.c
低記憶體管理器(Low Memory Killer),相對於Linux標準OOM(Out Of Memory)機制更加靈活,它可以根據需要殺死process來釋放需要的記憶體。
源代碼位於drivers/staging/android/lowmemorykiller.c
匿名共享記憶體(ashmem),為process間提供大塊共享記憶體,同時為內核提供回收和管理這個記憶體的機制。
源代碼位於mm/ashmem.c
Android PMEM(Physical),PMEM用於向用戶空間提供連續的物理記憶體區域,DSP和某些設備只能工作在連續的物理記憶體上。
源代碼位於drivers/misc/pmem.c
Android Logger,一個輕量級的日誌設備,用於抓取Android系統的各種日誌。
源代碼位於drivers/staging/android/logger.c
Android Alarm,提供了一個定時器用於把設備從睡眠狀態喚醒,同時它也提供了一個即使在設備睡眠時也會運行的時鐘基準,
源代碼位於drivers/rtc/alarm.c
USB Gadget驅動,一個基於標準Linux USB gadget驅動框架的設備驅動,Android的USB驅動是基於gaeget框架的,
源代碼位於drivers/usb/gadget/
Android Ram Console,為了提供調試功能,Android允許將調試日誌信息寫入一個被稱為RAM Console的設備裡,它是一個基於RAM的Buffer。
源代碼位於drivers/staging/android/ram_console.c。
Android timed device,提供了對設備進行定時控制功能,目前支持vibrator和LED設備。
源代碼位於drivers/staging/android/timed_output.c(timed_gpio.c)。
Yaffs2檔案系統,Android採用Yaffs2作為MTD nand flash檔案系統,源代碼位於fs/yaffs2/目錄下。Yaffs2是一個快速穩定的應用於NAND和NOR Flash的跨平台的嵌入式設備檔案系統,同其他Flash檔案系統相比,Yaffs2使用更小的記憶體來保存他的運行狀態,因此它佔用記憶體小;Yaffs2的垃圾回收非常簡單而且快速,因此能達到更好的性能;Yaffs2在大容量的NAND Flash上性能表現尤為明顯,非常適合大容量的Flash存儲。
Android內核添加或修改的文件很多,下面的列表描述了Android Emulator內核的文件:
drivers/misc/kernel_debugger.c]]>
drivers/misc/pmem.c
drivers/misc/qemutrace/qemu_trace_sysfs.c
drivers/misc/qemutrace/qemu_trace.c
drivers/misc/qemutrace/qemu_trace.h
drivers/misc/uid_stat.c
drivers/staging/android/lowmemorykiller.c
drivers/staging/android/logger.c
drivers/staging/android/timed_output.h
drivers/staging/android/ram_console.c
drivers/staging/android/timed_gpio.c
drivers/staging/android/logger.h
drivers/staging/android/binder.h
drivers/staging/android/binder.c
drivers/staging/android/timed_output.c
drivers/staging/android/timed_gpio.h
drivers/rtc/alarm.c
drivers/rtc/rtc-goldfish.c
drivers/net/pppolac.c
drivers/net/ppp_mppe.c
drivers/net/pppopns.c
drivers/video/goldfishfb.c
drivers/switch/switch_class.c
drivers/switch/switch_gpio.c
drivers/char/dcc_tty.c
drivers/char/goldfish_tty.c
drivers/watchdog/i6300esb.c
drivers/input/misc/gpio_event.c
drivers/input/misc/gpio_input.c
drivers/input/misc/gpio_output.c
drivers/input/misc/keychord.c
drivers/input/misc/gpio_axis.c
drivers/input/misc/gpio_matrix.c
drivers/input/keyreset.c
drivers/input/keyboard/goldfish_events.c
drivers/input/touchscreen/synaptics_i2c_rmi.c
drivers/usb/gadget/android.c
drivers/usb/gadget/f_adb.h
drivers/usb/gadget/f_mass_storage.h
drivers/usb/gadget/f_adb.c
drivers/usb/gadget/f_mass_storage.c
drivers/mmc/host/goldfish.c
drivers/power/goldfish_battery.c
drivers/leds/ledtrig-sleep.c
drivers/mtd/devices/goldfish_nand_reg.h
drivers/mtd/devices/goldfish_nand.c
kernel/power/earlysuspend.c
kernel/power/consoleearlysuspend.c
kernel/power/fbearlysuspend.c
kernel/power/wakelock.c
kernel/power/userwakelock.c
kernel/cpuset.c
kernel/cgroup_debug.c
kernel/cgroup.c
mm/ashmem.c
include/linux/ashmem.h
include/linux/switch.h
include/linux/keychord.h
include/linux/earlysuspend.h
include/linux/android_aid.h
include/linux/uid_stat.h
include/linux/if_pppolac.h
include/linux/usb/android.h
include/linux/wifi_tiwlan.h
include/linux/android_alarm.h
include/linux/keyreset.h
include/linux/synaptics_i2c_rmi.h
include/linux/android_pmem.h
include/linux/kernel_debugger.h
include/linux/gpio_event.h
include/linux/wakelock.h
include/linux/if_pppopns.h
net/ipv4/sysfs_net_ipv4.c
net/ipv4/af_inet.c
net/ipv6/af_inet6.c
net/bluetooth/af_bluetooth.c
security/commoncap.c
fs/proc/base.c
來源: busybox及bash在android中的安裝及init.rc修改
Ramdisk的製作
2.6內核開始,initrd.img採用cpio壓縮,ramdisk.img也一樣,使用gunzip解壓縮,然後再使用 cpio解包。
1)將ramdisk.img複製其他目錄,名稱改為ramdisk.img.gz,解壓
#gunzip ramdisk.img.gz
//新建一個文件夾ramdisk,進入
#cpio -i -F ../ramdisk.img
這時,可到ramdisk中看看去~
2)修改init.rc,在PATH中加上busybox 路徑
//busybox安裝在 /data/busybox
## Global environment setup
##
env {
#其中, /data/busybox 為busybox安裝路徑,bash也是放在其中
PATH /data/busybox:/sbin:/system/sbin:/system/bin
LD_LIBRARY_PATH /system/lib
ANDROID_BOOTLOGO 1
ANDROID_ROOT /system
ANDROID_ASSETS / system/app
ANDROID_DATA /data
EXTERNAL_STORAGE /sdcard
DRM_CONTENT /data/drm/content
HOME / #這個也是新添加環境變量
}
3)重新打包成鏡像,並使用新鏡像啟動emulator
#cpio -i -t -F ../ramdisk.img > list
#cpio -o -H newc -O rd_busybox.img < list
//使用 -ramdisk 參數,指定所使用的鏡像文件
#emulator -noskin -ramdisk rd_ramdisk.img
安裝busybox及bash
busybox 下載地址:http://www.billrocks.org/android_libs/bin/
註:也可自行交叉編譯,不過需靜態編譯。
#adb shell mkdir /data/busybox
#adb push busybox /data/busyobx
#adb push bash /data/busybox
//adb shell,進入android
#cd /data/busyobx
#chmod +x busybox bash
#./busybox --install //將程序安裝在當前目錄下
//重啟emulator,進入bash
bash-3.2# export
declare -x ANDROID_ASSETS="/system/app"
declare -x ANDROID_BOOTLOGO="1"
declare -x ANDROID_DATA="/data"
declare -x ANDROID_ROOT="/system"
declare -x DRM_CONTENT="/data/drm/content"
declare -x EXTERNAL_STORAGE="/sdcard"
declare -x HOME="/"
declare -x LD_LIBRARY_PATH="/system/lib"
declare -x OLDPWD
declare -x PATH="/data/busybox:/sbin:/system/sbin:/system/bin"
declare -x PWD="/data/bin/tst"
declare -x SHLVL="1"
註:在1中修改 init.rc 增加的環境變量及路徑已經生效。
]]>