在一臺運行 Linux 的計算機中,CPU 在任什麼時候候只會有以下四種狀態:html
【1】 在處理一個硬中斷。node
【2】 在處理一個軟中斷,如 softirq、tasklet 和 bh。linux
【3】 運行於內核態,但有進程上下文,即與一個進程相關。緩存
【4】 運行一個用戶態進程。數據結構
1. Linux中的進程間通訊機制源自於Unix平臺上的進程通訊機制。Unix的兩大分支AT&T Unix和BSD Unix在進程通訊實現機制上的各有所不一樣,前者造成了運行在單個計算機上的System V IPC,後者則實現了基於socket的進程間通訊機制。同時Linux也遵循IEEE制定的Posix IPC標準,在三者的基礎之上實現瞭如下幾種主要的IPC機制:管道(Pipe)及命名管道(Named Pipe),信號(Signal),消息隊列(Message queue),共享內存(Shared Memory),信號量(Semaphore),套接字(Socket)。經過這些IPC機制,用戶空間進程之間能夠完成互相通訊。爲了完成內核空間與用戶空間通訊,Linux提供了基於socket的Netlink通訊機制,能夠實現內核與用戶空間數據的及時交換。app
2. 到目前Linux提供了9種機制完成內核與用戶空間的數據交換,分別是內核啓動參數、模塊參數與 sysfs、sysctl、系統調用、netlink、procfs、seq_file、debugfs和relayfs,其中模塊參數與sysfs、procfs、debugfs、relayfs是基於文件系統的通訊機制,用於內核空間向用戶控件輸出信息;sysctl、系統調用是由用戶空間發起的通訊機制。因而可知,以上均爲單工通訊機制,在內核空間與用戶空間的雙向互動數據交換上略顯不足。Netlink是基於socket的通訊機制,因爲socket自己的雙共性、突發性、不阻塞特色,所以可以很好的知足內核與用戶空間小量數據的及時交互,所以在Linux 2.6內核中普遍使用,例如SELinux,Linux系統的防火牆分爲內核態的netfilter和用戶態的iptables,netfilter與iptables的數據交換就是經過Netlink機制完成。 socket
3 Netlink機制及其關鍵技術
3.1 Netlink機制
Linux操做系統中當CPU處於內核狀態時,能夠分爲有用戶上下文的狀態和執行硬件、軟件中斷兩種。其中當處於有用戶上下文時,因爲內核態和用戶態的內存映射機制不一樣,不可直接將本地變量傳給用戶態的內存區;處於硬件、軟件中斷時,沒法直接向用戶內存區傳遞數據,代碼執行不可中斷。針對傳統的進程間通訊機制,他們均沒法直接在內核態和用戶態之間使用,緣由以下表:async
通訊方法
沒法介於內核態與用戶態的緣由
管道(不包括命名管道)
侷限於父子進程間的通訊。
消息隊列
在硬、軟中斷中沒法無阻塞地接收數據。
信號量
沒法介於內核態和用戶態使用。
內存共享
須要信號量輔助,而信號量又沒法使用。
套接字
在硬、軟中斷中沒法無阻塞地接收數據。函數
4.post
解決內核態和用戶態通訊機制可分爲兩類:
處於有用戶上下文時,可使用Linux提供的copy_from_user()和copy_to_user()函數完成,但因爲這兩個函數可能阻塞,所以不能在硬件、軟件的中斷過程當中使用。
處於硬、軟件中斷時。
2.1 能夠經過Linux內核提供的spinlock自旋鎖實現內核線程與中斷過程的同步,因爲內核線程運行在有上下文的進程中,所以能夠在內核線程中使用套接字或消息隊列來取得用戶空間的數據,而後再將數據經過臨界區傳遞給中斷過程.
2.2 經過Netlink機制實現。Netlink 套接字的通訊依據是一個對應於進程的標識,通常定爲該進程的 ID。Netlink通訊最大的特色是對對中斷過程的支持,它在內核空間接收用戶空間數據時再也不須要用戶自行啓動一個內核線程,而是經過另外一個軟中斷調用用戶事先指定的接收函數。經過軟中斷而不是自行啓動內核線程保證了數據傳輸的及時性
5.本人也寫了這樣一個例程,能夠動態的將內核空間的物理地址和大小傳給用戶空間。本文也演示了內核空間和用戶空間進行通訊可使用的兩種經常使用方法:proc文件系統和mmap共享內存,整個內核模塊,在模塊插入時創建proc文件,分配內存。卸載模塊的時候將用戶空間寫入的內容打印出來。
如下是內核模塊的代碼和用戶空間的測試代碼。
*This program is used to allocate memory in kernel
and pass the physical address to userspace through proc file.*/
#include <linux/version.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/mm.h>
#define PROC_MEMSHARE_DIR "memshare"
#define PROC_MEMSHARE_PHYADDR "phymem_addr"
#define PROC_MEMSHARE_SIZE "phymem_size"
/*alloc one page. 4096 bytes*/
#define PAGE_ORDER 0
/*this value can get from PAGE_ORDER*/
#define PAGES_NUMBER 1
struct proc_dir_entry *proc_memshare_dir ;
unsigned long kernel_memaddr = 0;
unsigned long kernel_memsize= 0;
static int proc_read_phymem_addr(char *page, char **start, off_t off, int count)
{
return sprintf(page, "%08lx\n", __pa(kernel_memaddr));
}
static int proc_read_phymem_size(char *page, char **start, off_t off, int count)
{
return sprintf(page, "%lu\n", kernel_memsize);
}
static int __init init(void)
{
/*build proc dir "memshare"and two proc files: phymem_addr, phymem_size in the dir*/
proc_memshare_dir = proc_mkdir(PROC_MEMSHARE_DIR, NULL);
create_proc_info_entry(PROC_MEMSHARE_PHYADDR, 0, proc_memshare_dir, proc_read_phymem_addr);
create_proc_info_entry(PROC_MEMSHARE_SIZE, 0, proc_memshare_dir, proc_read_phymem_size);
/*alloc one page*/
kernel_memaddr =__get_free_pages(GFP_KERNEL, PAGE_ORDER);
if(!kernel_memaddr)
{
printk("Allocate memory failure!\n");
}
else
{
SetPageReserved(virt_to_page(kernel_memaddr));
kernel_memsize = PAGES_NUMBER * PAGE_SIZE;
printk("Allocate memory success!. The phy mem addr=%08lx, size=%lu\n", __pa(kernel_memaddr), kernel_memsize);
}
return 0;
}
static void __exit fini(void)
{
printk("The content written by user is: %s\n", (unsigned char *) kernel_memaddr);
ClearPageReserved(virt_to_page(kernel_memaddr));
free_pages(kernel_memaddr, PAGE_ORDER);
remove_proc_entry(PROC_MEMSHARE_PHYADDR, proc_memshare_dir);
remove_proc_entry(PROC_MEMSHARE_SIZE, proc_memshare_dir);
remove_proc_entry(PROC_MEMSHARE_DIR, NULL);
return;
}
module_init(init);
module_exit(fini);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Godbach ([email]nylzhaowei@163.com[/email])");
MODULE_DESCRIPTION("Kernel memory share module.");
用戶空間的測試代碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/mman.h>
int main(int argc, char* argv[])
{
if(argc != 2)
{
printf("Usage: %s string\n", argv[0]);
return 0;
}
unsigned long phymem_addr, phymem_size;
char *map_addr;
char s[256];
int fd;
/*get the physical address of allocated memory in kernel*/
fd = open("/proc/memshare/phymem_addr", O_RDONLY);
if(fd < 0)
{
printf("cannot open file /proc/memshare/phymem_addr\n");
return 0;
}
read(fd, s, sizeof(s));
sscanf(s, "%lx", &phymem_addr);
close(fd);
/*get the size of allocated memory in kernel*/
fd = open("/proc/memshare/phymem_size", O_RDONLY);
if(fd < 0)
{
printf("cannot open file /proc/memshare/phymem_size\n");
return 0;
}
read(fd, s, sizeof(s));
sscanf(s, "%lu", &phymem_size);
close(fd);
printf("phymem_addr=%lx, phymem_size=%lu\n", phymem_addr, phymem_size);
/*memory map*/
int map_fd = open("/dev/mem", O_RDWR);
if(map_fd < 0)
{
printf("cannot open file /dev/mem\n");
return 0;
}
map_addr = mmap(0, phymem_size, PROT_READ|PROT_WRITE, MAP_SHARED, map_fd, phymem_addr);
strcpy(map_addr, argv[1]);
munmap(map_addr, phymem_size);
close(map_fd);
return 0;
}
測試的內核是2.6.25.如下是執行結果。
debian:/home/km/memshare# insmod memshare_kernel.ko
debian:/home/km/memshare# ./memshare_user 'hello,world!'
phymem_addr=e64e000, phymem_size=4096
debian:/home/km/memshare# cat /proc/memshare/phymem_addr
0e64e000
debian:/home/km/memshare# cat /proc/memshare/phymem_size
4096
debian:/home/km/memshare# rmmod memshare_kernel
debian:/home/km/memshare# tail /var/log/messages
Sep 27 18:14:24 debian kernel: [50527.567931] Allocate memory success!. The phy mem addr=0e64e000, size=4096
Sep 27 18:15:31 debian kernel: [50592.570986] The content written by user is: hello,world!
6.
linux內核空間和用戶空間通訊
做者:harvey wang
新浪博客地址:http://blog.sina.com.cn/harveyperfect,有關於減肥和學習英語相關的博文,歡迎交流
因網上已有不少介紹各類通訊方式的示例代碼,因此在本文中只是給出各類內核空間和用戶空間通訊方式的介紹說明。但願給像我同樣的初學者提供必定的指導。因水平有限,歡迎各位批評指點。
1 概述
Linux內核將這4G字節的空間分爲兩部分。將最高的1G字節(從虛擬地址0xC0000000到0xFFFFFFFF),供內核使用,稱爲「內核空間」。而將較低的3G字節(從虛擬地址0x00000000到0xBFFFFFFF),供各個進程使用,稱爲「用戶空間「)。除了進程之間的通訊外,在嵌入式設計中還常常須要進行內核空間和用戶空間的信息交互。本文主要討論內核空間和用戶空間信息交互的方法。
1.1 處理器狀態
處理器總處於如下狀態中的一種:
A、內核態,運行於進程上下文,內核表明進程運行於內核空間;
B、內核態,運行於中斷上下文,包括硬中斷和軟中斷;
C、用戶態,運行於用戶空間。
1.2 不一樣狀態的限制
根據上面的狀態分類,內核空間和用戶空間之間的信息交互就分爲兩類,即中斷上下文內核態空間與進程空間信息交互;進程上下文內核態空間和進程空間信息交互。
內核態環境 |
進入內核態的方式 |
侷限性 |
說明 |
進程上下文 |
在進程中經過系統調用進入內核態,內核態代碼與該進程相關。 |
內核空間和進程空間的虛擬地址不一樣,不能直接傳遞信息。 |
該進程的頁表基地址依然在頁表基地址寄存器(如X86中的CR3)中,內核空間中可使用__user 強制使用用戶空間的地址,從而進行數據交互。 |
中斷上下文 |
硬件觸發中斷,或內核中掛接軟中斷。不與特定的進程相關。 |
內核空間和進程空間的虛擬地址不一樣,不能直接傳遞信息。 中斷中不能睡眠,不能運行引發阻塞的函數。 |
因爲中斷觸發的隨機性,中斷上下文內核態不與特定的進程相關。 |
2 各類通訊方式
本節說明各類通訊方式是否適合內核空間和用戶空間信息交互,以及如何使用。
2.1 信號
在進程中使用函數signal()或sigaction()安裝信號時指定了關聯的函數。在內核空間相進程發送信號,從內核空間返回進程空間時檢查並執行相應的關聯函數。
在進程中可使用pause()函數進入睡眠,在有信號產生並執行了相應的關聯函數後進程被喚醒,繼續執行。可使用這種方式實現內核空間和用戶空間的同步。
pause()會使當前進程掛起,直到捕捉到一個信號,對指定爲忽略的信號,pause()不會返回。只有執行了一個信號處理函數,並從其返回,puase()才返回-1,並將errno設爲EINTR。
2.2 信號量
雖然原理同樣,但內核空間和用戶空間的信號量是徹底兩套系統,因此信號量不能用於內核空間和用戶空間信息交互。
2.3 無名管道
無名管道只適用於有關係的進程之間通訊。不能用於內核空間和用戶空間信息交互。
2.4 get_user()/put_user()
get_user(x, ptr):本函數是在內核中被調用,獲取用戶空間指定地址的數值(一個字節或字)並保存到內核變量x中,ptr爲用戶空間的地址。用法舉例以下:get_user(val, (int __user *)arg)
put_user(x, ptr):在內核中被調用,將內核空間的變量x的數值(一個字節或字)保存到用戶空間指定地址處,prt爲用戶空間地址。用法舉例以下:put_user(val, (int __user *)arg)
註明:函數用於進程上下文內核態空間,即一般在系統調用函數中使用該函數,如設備驅動中ioctl函數中。
2.5 copy_from_user()/copy_to_user()
主要應用於設備驅動中讀寫函數中,經過系統調用觸發,在當前進程上下文內核態運行(即當前進程經過系統調用觸發)。
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
一般用在設備讀函數或ioctl中獲取參數的函數中:其中「to」是用戶空間的buffer地址,在本函數中將內核buffer「from」除的n個字節拷貝到用戶空間的「to」buffer。
unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)
一般用在設備寫函數或ioctl中設置參數的函數中:「to」是內核空間的buffer指針,要寫入的buffer;「from」是用戶空間的指針,數據源buffer。
注意:中斷代碼時不能用這兩個函數,由於其調用了might_sleep()函數,會致使睡眠,而且這兩個函數要求在進程上下文內核態空間中運行。
這兩個函數不能直接在中斷中使用,可是能夠變通一下,在中斷中向進程發送信號通知進程有數據準備好。在進程執行時調用read函數,在read函數中調用copy_to_user函數,從而實現中斷觸發,把數據從內核空間拷貝到用戶空間的須要。
2.6 共享內存(mmap)
使用mmap()函數一般映射一個普通文件實現進程之間內存共享,即多個進程打開同一個文件,將文件映射到各自進程的虛擬空間。這樣各個進程就能夠經過共享的內存進行大量的數據交互,固然須要咱們本身設計互斥功能。
還可使用mmap()函數實現內核空間和用戶空間內存共享的功能。網上提到的方法基本都是proc文件+mmap。
大致過程以下
一、在模塊中申請一些內存頁面,做爲共享的內存空間。
二、建立可讀的proc文件,在其讀函數中把上面申請的內存空間的物理地址返回給進程空間。
三、在進程空間open /dev/mem文件,並把從proc讀取的物理地址(要共享的內存的物理地址)做爲文件/dev/mem的offset,以此offset 把/dev/mem文件的若干空間用mmap映射到進程空間。
注意:
一、/dev/mem 不是一個普通的文件裏面的內容是全部物理內存的內容信息。因此,在上面的過程當中把共享空間的物理地址做爲offset使用。
二、proc文件的做用就是提供一個讀取的函數,把共享內存的地址從內核空間傳遞到用戶空間。也能夠用設備的ioctl 把該物理地址數值傳給用戶空間。
7.
爲了更好的理解和描述通訊原理,須要先認識一下4個重要的數據結構,分別是file_operations,file,inode,dentry。
文件操做集
file_operations結構包含了一組函數指針,每一個打開的文件(在內核裏面由file結構表示)和一組函數關聯(經過file結構中指向file_operations結構的f_op字段)。這些操做組要用來實現系統調用,例如open,read等等。
file結構
file結構表示一個打開的文件(系統中每一個打開的文件在內核空間都對應一個file結構)。它由內核在open時建立,由close釋放(進程撤銷時也會釋放)。
file結構中最重要的成員羅列以下:
loff_t f_pos;
當前讀/寫位置
struct file_operations *f_op;
指向一個文件操做集結構。內核在執行open時對這個指針賦值,之後要處理相關係統調用時就直接調用這個指針。
例如:write()系統調用時將直接調用file->f_op->write()。
struct dentry *f_dentry;
文件對應的目錄項結構。能夠經過file->f_dentry->d->d_inode來訪問索引節點。
inode結構
內核用inode結構在內部表示文件,它和file結構不一樣,後者表示打開的文件描述符,對於一個文件,可能會有許多個表示打開的文件描述符的file結構,但它們都指向惟一的inode對象。
inode結構中最重要的成員羅列以下:
dev_t i_rdev;
對錶示設備文件的inode結構,該字段包含了設備標號(主/次設備號)。
struct cdev *i_cdev;
指向一個字符設備驅動程序對象。
const struct inode_operations*i_op;
指向索引結點操做集。
例如,系統調用mkdir將會調用這裏的函數inode->i_op->mkdir()
const struct file_operations*i_fop;
指向文件操做集。當執行open時,將使用這個這裏的指針賦值file結構的f_op。
dentry結構
對於進程查找的每一個路徑名的份量,內核都爲其建立一個dentry結構。
例如,在查找路徑名/dev/test時,內核爲根目錄「/」建立一個dentry結構,爲dev建立二級dentry結構,爲test建立三級dentry結構。
經典的內核與用戶空間的通訊(使用read和write系統調用)
1. 用戶空間須要將某些數據傳遞給內核,並指定數據的處理函數。
2. 用戶空間須要從內核讀取數據,並指定數據的讀取函數。
當執行open時,file結構的f_op將會指向要求的操做集函數集,也就是inode結構中的i_fop。通訊的實現機制就是,註冊inode結構,令inode的i_fop指向要求的操做集函數集,當系統打開這個文件時,使用read和write系統調用時,將會調用要求的操做函數。
用於通訊的文件系統是一種特殊的文件系統,在系統初始化時通常只有一個根inode和根dentry結構,每一個inode和dentry結構對象都對應一些內核數據和內核操做函數集。
例如:proc文件系統,初始化時只創建根路徑/proc的inode和dentry結構,當第一次訪問/proc下的某個文件時,將從根dentry結構開始查找並創建相應的inode和dentry結構,並將這些結構加入相應的緩存,當第二次訪問時,能夠直接從緩存中得到該結構。
這裏就會涉及到4個關鍵的問題:
下面將會圍繞以上4個問題分析4種最重要的通訊機制:
1. misc設備(可讀,可寫,可進行內存映射)
2. proc(可讀,可寫,通常不進行內存映射)
3. sysctl(可讀,可寫)
4. seq_file(只讀)
每一個misc設備在/dev下都有一個設備文件,/dev下的每一個設備文件都對應一個inode對象,一個misc設備用struct miscdevice結構來表示,以下:
struct miscdevice {
int minor;次設備號
const char *name;設備名稱
const struct file_operations *fops;設備操做函數集
struct list_head list;
struct device *parent;
struct device *this_device;
const char *nodename;
mode_t mode;
};
下面將分析如下幾個過程:
1. 如何根據miscdevice結構在/dev下創建設備文件?
2. miscdevice結構是如何組織的?
3. 如何根據/dev下的設備文件定位miscdevice結構?
將會涉及到的概念:
主設備號:一般是標識設備對應的設備驅動程序(也能夠說是標識設備的類別)。
次設備號:在某個類裏面肯定一個具體的設備。
inode中與設備有關的成員變量以下:
dev_t i_rdev:設備編號,包括主設備號,次設備號。
struct cdev * i_cdev:指向字符設備驅動程序,根據主設備號定位。
其中misc設備的主設備號是10。
例子:註冊一個misc設備
static struct file_operations audio_fops = { 定義操做函數集
.owner = THIS_MODULE,
.open = audio_in_open,
.release = audio_in_release,
.read = audio_in_read,
.write = audio_in_write,
.unlocked_ioctl = audio_in_ioctl,
};
struct miscdevice audio_in_misc = { misc設備對象
.minor = MISC_DYNAMIC_MINOR,次設備號
.name = "msm_pcm_in",設備名稱
.fops = &audio_fops,操做函數集
};
static int __init audio_in_init(void)
{
misc_register(&audio_in_misc);向內核註冊misc設備
return 1;
}
這個例子會在/dev下生成一個設備文件,文件名是"msm_pcm_in",文件對應的inode->rdev的主設備號是10,次設備號是MISC_DYNAMIC_MINOR,inode關聯的操做函數集是audio_fops。
在/dev下創建inode對象的函數調用流程以下:
int misc_register(struct miscdevice * misc)à
struct device *device_create()à
struct device *device_create_vargs()à
int device_register(struct device *dev)à
int device_add(struct device *dev)à
int devtmpfs_create_node(struct device *dev)à
int vfs_mknod(struct inode *dir, struct dentry *dentry, int mode, dev_t dev)à
error = dir->i_op->mknod(dir, dentry, mode, dev);à
不一樣的文件系統i_op->mknod()指向不一樣的函數。
例如,若是/dev是ext3文件系統,則調用:
static int ext3_mknod ()à接着調用如下函數初始化inode對象
init_special_inode(inode, inode->i_mode, rdev);函數定義以下:
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {misc設備是一種特殊的字符設備
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;設備號
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o) for"
" inode %s:%lu\n", mode, inode->i_sb->s_id,
inode->i_ino);
}
通過這個過程創建的dentry結構和inode結構如圖2-1所示,圖中的i_rdev是用於定位某個struct miscdevice結構。
圖 2-1 misc設備文件dentry結構和inode結構
其中const struct file_operations def_chr_fops = {
.open = chrdev_open,
};
如今inode對象創建完畢,這個inode對象只包含了兩個信息:
1. 設備號,2.字符設備操做函數集def_chr_fops。
下面分析如何根據這兩個信息定位miscdevice結構。
當用戶程序執行open()打開/dev下的misc設備文件時,內核首先會調用inode->i_fop->open(),也就是chrdev_open(),以下:
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {若是字符設備驅動程序對象爲空
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);//根據i_rdev內的主設備號
if (!kobj) //查找並返回驅動程序對象的kobj對象。
return -ENXIO;
new = container_of(kobj, struct cdev, kobj);//根據kobj對象地址得到設備驅動程序對象
spin_lock(&cdev_lock);
/* Check i_cdev again in case somebody beat us to it while
we dropped the lock. */
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new; //關聯設備驅動程序對象
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
ret = -ENXIO;
filp->f_op = fops_get(p->ops); //得到設備驅動程序對象的操做函數
if (!filp->f_op)
goto out_cdev_put;
if (filp->f_op->open) {
ret = filp->f_op->open(inode,filp); //調用設備驅動程序的open()函數
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
上邊涉及到struct kobject結構,在/sys下顯示的每一個文件都對應一個kobject,通常每一個kobject對象又嵌入到另外一個對象中,例如:
struct cdev {字符設備驅動程序對象
struct kobject kobj; 內嵌的kobject對象
struct module *owner;
const struct file_operations *ops;操做函數集
struct list_head list;
dev_t dev; 主設備號
unsigned int count;
};
知道了內嵌的kobject對象的地址,再作一個偏移也就獲得了cdev對象的地址。
每一個misc設備是主設備號爲10的字符設備,內核在misc_init()中註冊了 misc字符設備的驅動程序對象,以下:
register_chrdev(MISC_MAJOR,"misc",&misc_fops)
其中主設備號 MISC_MAJOR = 10
驅動程序操做函數集:static const struct file_operations misc_fops = {
.owner = THIS_MODULE,
.open = misc_open,
};
chrdev_open()函數功能解釋:
1. 根據設備號i_rdev中的主設備號(10)找到kobject對象à
2. 根據kobject對象找到struct cdev對象(「misc」)à
3. 調用struct cdev對象(「misc」)的open()函數(也就是misc_open())。
static int misc_open(struct inode * inode, struct file * file)
{
int minor = iminor(inode); 得到次設備號
struct miscdevice *c;
int err = -ENODEV;
const struct file_operations *old_fops, *new_fops = NULL;
mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) { 根據次設備號查找misc設備鏈表
if (c->minor == minor) {
new_fops = fops_get(c->fops);找到miscdevice操做函數
break;
}
}
if (!new_fops) {
mutex_unlock(&misc_mtx);
request_module("char-major-%d-%d", MISC_MAJOR, minor);
mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) {
if (c->minor == minor) {
new_fops = fops_get(c->fops);
break;
}
}
if (!new_fops)
goto fail;
}
err = 0;
old_fops = file->f_op;
file->f_op = new_fops;賦予新的操做函數
if (file->f_op->open) {
file->private_data = c;
err=file->f_op->open(inode,file);執行miscdevice的open函數
if (err) {
fops_put(file->f_op);
file->f_op = fops_get(old_fops);
}
}
fops_put(old_fops);
fail:
mutex_unlock(&misc_mtx);
return err;
}
執行misc_open()函數以後,file對象的f_op將指向miscdevice對象的fops。
上邊還涉及到2個查找過程:(圖2-2,圖2-3展現了查找過程)
1. 如何根據主設備號(10)找到對應的字符設備驅動程序?
2. 如何根據次設備號找到miscdevice對象?
圖2-2是字符設備驅動程序對象的組織結構,其中cdev_map是全局變量,每一個probe都對應一個字符設備驅動程序對象,其中probe->data指向字符設備驅動程序對象,使用register_chrdev()就是向這裏註冊一個字符設備驅動程序對象,其它內核路徑將會根據主設備號在這個表格內查找對應的字符設備驅動程序。
圖 2-2 字符設備驅動程序對象組織結構
圖2-3是miscdevice對象的組織結構,其中misc_list是全局變量,chrdev_open()將根據次設備號找到對應的miscdevice對象,也就找到了咱們須要關聯的操做函數集。
圖 2-3 misc設備組織
1. 創建struct miscdevice結構,其中的成員變量fops指向咱們本身定義的操做函數集。
2. 調用misc_register()註冊miscdevice結構,將會在/dev下生成一個設備文件。
3. 使用open()打開這個設備文件,則file對象的f_op將會指向咱們本身定義的操做函數集。
例如用戶空間使用read()函數,將會定位到,miscdevice->fops->read()
使用系統調用read()和write()是一種經典的通訊流程,它必須進行數據的拷貝,也就是說,用戶空間想使用內核空間的數據,必須將這個數據拷貝到用戶空間,而內核空間想引用用戶空間的數據,也必須將數據拷貝到內核空間,效率是比較低的。
必須進行數據的拷貝緣由有2點:
1. 用戶空間使用的線性地址是在前3個G的空間,而內核使用的線性地址是在第4個G,因此用戶空間無法使用內核中的線性地址。
2. 當前運行進程是不斷替換的,內核是使用當前運行進程的頁表,雖然不一樣進程的頁表的內核空間部分是相同的,可是用戶空間部分是不一樣的,若是內核要長期使用某個用戶空間的數據,將會出現問題(一樣的線性地址,在不一樣的進程上下文中將會引用不一樣的物理地址)。
共享內存通訊方式
爲了不數據的拷貝,咱們可使用共享內存,也就是註冊 miscdevice->fops->mmap()。
int (*mmap) (struct file *filp, struct vm_area_struct *vm);
實現步驟:
1. 用戶空間執行mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)。內核最終會調用miscdevice->fops->mmap()。
2. 在調用這個函數以前,內核會作一些預備工做:根據用戶空間提交的參數分配一塊線性空間(前3個G的某個空間),分配的線性空間用結構struct vm_area_struct描述。
3. 最終在miscdevice->fops->mmap()內能夠將某塊物理內存映射到這個線性空間。
通過以上步驟,用戶空間的某塊線性空間和內核的某塊線性空間將會指向相同的物理地址。
這種通訊方式應該是最快的,由於沒有數據的拷貝,也沒有額外的函數調用,僅僅是內存的讀取和寫入。很大的缺點就是沒有同步機制。
procfs是比較老的一種用戶態與內核態的數據交換方式,內核的不少數據都是經過這種方式出口給用戶的,內核的不少參數也是經過這種方式來讓用戶方便設置的。procfs提供的大部份內核參數是隻讀的。實際上,不少應用嚴重地依賴於procfs,所以它幾乎是必不可少的組件。
procfs提供了以下API:
struct proc_dir_entry *create_proc_entry(const char *name, mode_t mode,
struct proc_dir_entry *parent)
該函數用於建立一個正常的proc條目,參數name給出要創建的proc條目的名稱,參數mode給出了創建的該proc條目的訪問權限,參數parent指定創建的proc條目所在的目錄。若是要在/proc下創建proc條目,parent應當爲NULL。不然它應當爲proc_mkdir返回的struct proc_dir_entry結構的指針。
struct proc_dir_entry *proc_mkdir(const char * name, struct proc_dir_entry *parent)
該函數用於建立一個proc目錄,參數name指定要建立的proc目錄的名稱,參數parent爲該proc目錄所在的目錄。
例子:建立一個proc下的文件
int exam_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
{讀函數
count = sprintf(page, "%d", *(int *)data);
return count;
}
int exam_write_proc(struct file *file, const char __user *buffer, unsigned long count, void *data)
{寫函數
printk(「exam write\n」);
return 3;
}
static int __init procfs_exam_init(void)
{
struct proc_dir_entry * entry;
entry = create_proc_entry("examproc", 0644, NULL);註冊proc_dir_entry結構
if (entry) {
entry->data = &string_var;
entry->read_proc = &exam_read_proc;賦讀函數地址
entry->write_proc = &exam_write_proc;賦寫函數地址
}
return 0;
}
static void __exit procfs_exam_exit(void)
{
remove_proc_entry("examproc", NULL);撤銷proc_dir_entry結構
}
module_init(procfs_exam_init);
module_exit(procfs_exam_exit);
這個模塊會在/proc目錄下建立一個文件,文件名是「examproc」,讀文件時將執行exam_read_proc(),寫文件時將執行exam_write_proc()。
注意到這裏並無涉及dentry對象和inode對象,下面將說明如何根據proc_dir_entry創建相應的dentry對象和inode對象。
proc下面的每一個目錄和文件都對應一個struct proc_dir_entry對象,proc_dir_entry對象組織結構如圖3-1所示:
圖3-1 proc_dir_entry對象組織結構
和/proc目錄同樣proc_dir_entry對象也組成樹狀結構,/proc下的每一個目錄和文件都對應一個proc_dir_entry對象。
在proc文件系統加載之初,只有根目錄/proc已經生成對應的dentry對象和inode對象,
如圖3-2所示,/proc下的其它目錄和文件在第一被訪問到時,纔開始創建對應dentry對象和inode對象。
從圖3-2中已經能夠看出proc文件系統是如何根據proc_dir_entry創建起dentry對象和inode對象。圖3-2的proc_dir_entry對應目錄/proc。
圖3-2 proc根目錄對象
其中static const struct inode_operationsproc_dir_inode_operations = {
.lookup = proc_lookup,
.getattr = proc_getattr,
.setattr = proc_notify_change,
};
static const struct file_operations proc_dir_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
.readdir = proc_readdir,
};
當系統第一次查找/proc下的某個文件時,將以這個dentry對象做爲入口點。
例如:要打開文件/proc/examproc,
1. 根據路徑/proc獲得proc根dentry對象,獲得inode對象地址,偏移獲得對應的proc_inode對象,接着獲得對應的dir_proc_dentry對象。
2. 查找1中的dir_proc_dentry對象的子對象,查看是否存在名字是examproc的dir_proc_dentry對象。
3. 若是2中找到對應的dir_proc_dentry對象,將會根據這個對象創建dentry對象和inode對象。
圖3-2所示的是非文件dentry對象,這裏要創建的是一個文件dentry對象,結構如圖3-3所示:
圖3-3 proc文件目錄對象結構
其中static const struct inode_operationsproc_file_inode_operations = {
.setattr = proc_notify_change,
};
static const struct file_operations proc_file_operations = {
.llseek = proc_file_lseek,
.read = proc_file_read,
.write = proc_file_write,
};
圖3-2和圖3-3的不一樣點在於函數指針不一樣。
static ssize_t proc_file_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
{
struct proc_dir_entry *pde = PDE(file->f_path.dentry->d_inode);
ssize_t rv = -EIO;
if (pde->write_proc) {
spin_lock(&pde->pde_unload_lock);
if (!pde->proc_fops) {
spin_unlock(&pde->pde_unload_lock);
return rv;
}
pde->pde_users++;
spin_unlock(&pde->pde_unload_lock);
/* FIXME: does this routine need ppos? probably... */
rv = pde->write_proc(file, buffer, count, pde->data);
pde_users_dec(pde);
}
return rv;
}
當執行write時將會執行proc_file_write(),接着將會調用pde->write_proc(file, buffer, count, pde->data);
執行read()函數是相似。
注意:當/proc下的某個文件第一次被訪問過以後,系統會將相應的目錄對象和inode加入相應的緩存,之後就從相應的緩存讀取目錄對象或者inode對象。
sysctl是一種用戶應用來設置和得到運行時的內核的配置參數的一種有效方式,經過這種方式,用戶應用能夠在內核運行的任什麼時候刻來改變內核的配置參數,也能夠在任什麼時候候得到內核的配置參數,一般,內核的這些配置參數也出如今proc文件系統的/proc/sys目錄下,用戶應用能夠直接經過這個目錄下的文件來實現內核配置的讀寫操做,例如,用戶能夠經過
cat /proc/sys/net/ipv4/ip_forward
來得知內核IP層是否容許轉發IP包,用戶能夠經過
echo 1 > /proc/sys/net/ipv4/ip_forward
把內核 IP 層設置爲容許轉發 IP包,即把該機器配置成一個路由器或網關。
先從一個實際的例子講起。
struct ctl_path net_ipv4_ctl_path[] = {
{ .procname = "net", },
{ .procname = "ipv4", },
{ },
};
static struct ctl_table ipv4_net_table[] = {
{
.procname = "icmp_echo_ignore_all",
.data = &init_net.ipv4.sysctl_icmp_echo_ignore_all,
.maxlen = sizeof(int),
.mode = 0644,
.proc_handler = proc_dointvec
},
{ }
};
__register_sysctl_paths(&net_sysctl_root,
&namespaces, net_ipv4_ctl_path, ipv4_net_table);
這個例子將會在/proc/sys/net/ipv4下創建一個文件icmp_echo_ignore_all,當對這個文件進行讀寫時,其實是對整形變量init_net.ipv4.sysctl_icmp_echo_ignore_all進行讀寫。
下面看一下內核是如何實現的。
首先會生成以下的結構,並加入struct ctl_table_header root_table_header的中鏈表。
圖4-1 sysctl目錄樹組織結構
當/proc/sys下的某個目錄和文件被第一次訪問時,就是根據這個結構進行查找和生成對應的dentry和inode對象。
sysctl是proc下的特殊的目錄,它使用本身的dentry和inode對象生成函數。
/proc/sys下的根目錄結構如圖4-2所示:
圖4-2 proc_sys根目錄對象
其中static const struct inode_operationsproc_sys_dir_operations= {
.lookup = proc_sys_lookup, sys子目錄搜索生成函數
.permission = proc_sys_permission,
.setattr = proc_sys_setattr,
.getattr = proc_sys_getattr,
};
當第一次搜索/proc/sys某個文件時,將會以這個根目錄爲入口點。
首先根據路徑/proc/sys生成對應的目錄和inode對象,接着能夠得到root_table_header,而後將會得到struct ctl_table_header鏈表,遍歷這個鏈表,找到是否有匹配的ctl_table對象,並生成對應的目錄和inode對象。/proc/sys下的文件對應的結構如圖4-3所示:
圖4-3 sysctl文件目錄對象
其中static const struct file_operationsproc_sys_file_operations = {
.read = proc_sys_read,
.write = proc_sys_write,
};
下面分析一下proc_sys_write函數
static ssize_t proc_sys_write(struct file *filp, const char __user *buf,
size_t count, loff_t *ppos)
{
return proc_sys_call_handler(filp, (void __user *)buf, count, ppos, 1);
}
static ssize_t proc_sys_call_handler(struct file *filp, void __user *buf,
size_t count, loff_t *ppos, int write)
{
struct inode *inode = filp->f_path.dentry->d_inode;得到inode對象
struct ctl_table_header *head = grab_header(inode);得到控制結構
struct ctl_table *table = PROC_I(inode)->sysctl_entry;得到ctl_table對象
ssize_t error;
size_t res;
if (IS_ERR(head))
return PTR_ERR(head);
/*
* At this point we know that the sysctl was not unregistered
* and won't be until we finish.
*/
error = -EPERM;
if (sysctl_perm(head->root, table, write ? MAY_WRITE : MAY_READ))
goto out;
/* if that can happen at all, it should be -EINVAL, not -EISDIR */
error = -EINVAL;
if (!table->proc_handler)
goto out;
/* careful: calling conventions are nasty here */
res = count;
error = table->proc_handler(table, write, buf, &res, ppos);
if (!error)
error = res;
out:
sysctl_head_finish(head);
return error;
}
從代碼能夠看出write系統調用在內核裏面就是根據ctl_table結構操做相應的函數和數據。
read系統調用相似。
通常地,內核經過在procfs文件系統下創建文件來向用戶空間提供輸出信息,用戶空間能夠經過任何文本閱讀應用查看該文件信息,可是procfs有一個缺陷,若是輸出內容大於1個內存頁,須要屢次讀,所以處理起來很難,另外,若是輸出太大,速度比較慢,有時會出現一些意想不到的狀況,Alexander Viro實現了一套新的功能,使得內核輸出大文件信息更容易,該功能出如今2.4.15(包括2.4.15)之後的全部2.4內核以及2.6內核中,尤爲是在2.6內核中,已經大量地使用了該功能。
要想使用seq_file功能,開發者須要包含頭文件linux/seq_file.h,並定義與設置一個seq_operations結構(相似於file_operations結構):
struct seq_operations {
void * (*start) (struct seq_file *m, loff_t *pos);
void (*stop) (struct seq_file *m, void *v);
void * (*next) (struct seq_file *m, void *v, loff_t *pos);
int (*show) (struct seq_file *m, void *v);
};
start函數用於指定seq_file文件的讀開始位置,返回實際讀開始位置,若是指定的位置超過文件末尾,應當返回NULL,start函數能夠有一個特殊的返回SEQ_START_TOKEN,它用於讓show函數輸出文件頭,但這隻能在pos爲0時使用,next函數用於把seq_file文件的當前讀位置移動到下一個讀位置,返回實際的下一個讀位置,若是已經到達文件末尾,返回NULL,stop函數用於在讀完seq_file文件後調用,它相似於文件操做close,用於作一些必要的清理,如釋放內存等,show函數用於格式化輸出,若是成功返回0,不然返回出錯碼。
Seq_file也定義了一些輔助函數用於格式化輸出:
int seq_putc(struct seq_file *m, char c);
函數seq_putc用於把一個字符輸出到seq_file文件。
int seq_puts(struct seq_file *m, const char *s);
函數seq_puts則用於把一個字符串輸出到seq_file文件。
int seq_escape(struct seq_file *, const char *, const char *);
函數seq_escape相似於seq_puts,只是,它將把第一個字符串參數中出現的包含在第二個字符串參數中的字符按照八進制形式輸出,也即對這些字符進行轉義處理。
int seq_printf(struct seq_file *, const char *, ...)
__attribute__ ((format (printf,2,3)));
函數seq_printf是最經常使用的輸出函數,它用於把給定參數按照給定的格式輸出到seq_file文件。
int seq_path(struct seq_file *, struct vfsmount *, struct dentry *, char *);
函數seq_path則用於輸出文件名,字符串參數提供須要轉義的文件名字符,它主要供文件系統使用。
在定義告終構struct seq_operations以後,用戶還須要把打開seq_file文件的open函數,以便該結構與對應於seq_file文件的struct file結構關聯起來,例如,struct seq_operations定義爲:
struct seq_operations exam_seq_ops = {
.start = exam_seq_start,
.stop = exam_seq_stop,
.next = exam_seq_next,
.show = exam_seq_show
};
那麼,open函數應該以下定義:
static int exam_seq_open(struct inode *inode, struct file *file)
{
return seq_open(file, &exam_seq_ops);
};
注意,函數seq_open是seq_file提供的函數,它用於把struct seq_operations結構與seq_file文件關聯起來。最後,用戶須要以下設置struct file_operations結構:
struct file_operations exam_seq_file_ops = {
.owner = THIS_MODULE,
.open = exm_seq_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
注意,用戶僅須要設置open函數,其它的都是seq_file提供的函數。
而後,用戶建立一個/proc文件並把它的文件操做設置爲exam_seq_file_ops便可:
struct proc_dir_entry *entry;
entry = create_proc_entry("exam_seq_file", 0, NULL);
if (entry)
entry->proc_fops = &exam_seq_file_ops;
對於簡單的輸出,seq_file用戶並不須要定義和設置這麼多函數與結構,它僅需定義一個show函數,而後使用single_open來定義open函數就能夠,如下是使用這種簡單形式的通常步驟:
1.定義一個show函數
int exam_show(struct seq_file *p, void *v)
{
…
}
2. 定義open函數
int exam_single_open(struct inode *inode, struct file *file)
{
return(single_open(file, exam_show, NULL));
}
注意要使用single_open而不是seq_open。
3. 定義struct file_operations結構
struct file_operations exam_single_seq_file_operations = {
.open = exam_single_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
注意,若是open函數使用了single_open,release函數必須爲single_release,而不是seq_release。在源代碼包中給出了一個使用seq_file的具體例子seqfile_exam.c,它使用seq_file提供了一個查看當前系統運行的全部進程的/proc接口,在編譯並插入該模塊後,用戶經過命令"cat /proc/ exam_esq_file"能夠查看系統的全部進程。
seq_file的實現其實很簡單,就是/proc下的一個文件,不過它不使用proc_file_read(),而是使用seq_read()函數進行數據的讀取,而且還提供了一些函數以方便數據的格式化讀取。
圖F-1 4個重要的數據結構
其中
struct file {
/*
* fu_list becomes invalid after file_free is called and queued via
* fu_rcuhead for RCU freeing
*/
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op;
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
/*
* NOTE:
* read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl
* can be called without the big kernel lock held in all filesystems.
*/
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
struct dentry {
atomic_t d_count;
unsigned int d_flags; /* protected by d_lock */
spinlock_t d_lock; /* per dentry lock */
int d_mounted;
struct inode *d_inode; /* Where the name belongs to - NULL is
* negative */
/*
* The next three fields are touched by __d_lookup. Place them here
* so they all fit in a cache line.
*/
struct hlist_node d_hash; /* lookup hash list */
struct dentry *d_parent; /* parent directory */
struct qstr d_name;
struct list_head d_lru; /* LRU list */
/*
* d_child and d_rcu can share memory
*/
union {
struct list_head d_child; /* child of parent list */
struct rcu_head d_rcu;
} d_u;
struct list_head d_subdirs; /* our children */
struct list_head d_alias; /* inode alias list */
unsigned long d_time; /* used by d_revalidate */
const struct dentry_operations *d_op;
struct super_block *d_sb; /* The root of the dentry tree */
void *d_fsdata; /* fs-specific data */
unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};
struct inode {
struct hlist_node i_hash;
struct list_head i_list; /* backing dev IO list */
struct list_head i_sb_list;
struct list_head i_dentry;
unsigned long i_ino;
atomic_t i_count;
unsigned int i_nlink;
uid_t i_uid;
gid_t i_gid;
dev_t i_rdev;
unsigned int i_blkbits;
u64 i_version;
loff_t i_size;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
blkcnt_t i_blocks;
unsigned short i_bytes;
umode_t i_mode;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
struct mutex i_mutex;
struct rw_semaphore i_alloc_sem;
const struct inode_operations *i_op;
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct super_block *i_sb;
struct file_lock *i_flock;
struct address_space *i_mapping;
struct address_space i_data;
#ifdef CONFIG_QUOTA
struct dquot *i_dquot[MAXQUOTAS];
#endif
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_mark_entries; /* fsnotify mark entries */
#endif
#ifdef CONFIG_INOTIFY
struct list_head inotify_watches; /* watches on this inode */
struct mutex inotify_mutex; /* protects the watches list */
#endif
unsigned long i_state;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned int i_flags;
atomic_t i_writecount;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
void *i_private; /* fs or device private pointer */
};