淺析內核與用戶層通訊的四種方法

方法列表:html

1.系統調用node

2.虛擬文件系統
  proc文件系統
  sysfs文件系統
  debugfs文件系統linux

3.ioctl接口git

4.netlink
調試方法:
    https://blog.csdn.net/gatieme/article/details/68948080shell

一:系統調用
1.簡介編程

  • 優秀博客:

          https://blog.csdn.net/gatieme/article/details/50779184
          https://blog.csdn.net/liduxun/article/details/48119849
          https://www.cnblogs.com/zl1991/p/6543634.htmlapi

  • 內核提供了用戶進程與內核進行交互的一組接口。
  • 讓應用程序受限地訪問硬件設備,提供了建立新進程並與已有進程進行通訊的機制,也提供了申請操做系統其它資源的能力。
  • 做用:

          1.爲用戶空間提供了一種硬件的抽象接口。
          2.系統調用保證了系統的穩定性和安全。
          3.系統調用是用戶空間訪問內核的惟一手段:除異常和陷入外,它們是內核惟一的合法入口。實際上,其它的像設備文件和/proc之類的方式,最終也仍是要經過系統調用進行訪問的。緩存

  • API、POSIX和C庫

  1.通常狀況下,應用程序經過在用戶空間實現的應用編程接口(API)而不是直接經過系統調用來編程。(好處:更大的兼容性,而無論系統調用的實現。)安全

2.在unix世界中,最流行的的應用編程接口是基於POSIX標準的。POSIX是由IEEE的標準組成,其目的是提供一套大致上基於Unix的可移植操做系統標準。POSIX是說明API和系統調用之間關係的一個極好例子。
3.C庫實現了Unix系統的主要API,包括標準C庫函數和系統調用接口。C庫提供了POSIX的絕大部分API。
4.Unix的系統調用抽象出了用於完成某種肯定的目的的函數。「提供機制而不是策略」數據結構

  • 要訪問系統調用(在Linux中常稱做syscall),一般經過C庫中定義的函數調用來進行。
  • 系統調用最終具備一種明確的操做。如:gitpid()系統調用,根據定義它會返回當前進程的PID.

在內核中的實現:

SYSCALL_DEFINE0(getpid) { return task_tgid_vnr(current); //return current->tgid
}
  • SYSCALL_DEFINE0:只是一個宏,它定義一個無參數的系統調用(所以這裏爲數字0),展開後代碼:
asmlinkage long sys_getpid(void)

 

  • 如何定義系統調用:

  1.注意函數聲明中 asmlinkage限定詞,這是一個編譯指令,通知編譯器僅從棧中提取該函數的參數。全部的系統調用都須要這個限定詞。
  2.函數返回long。爲保證32位和64位系統的兼容,系統調用在用戶空間和內核空間有不一樣的返回類型,在用戶空間爲int,在內核空間爲long。
  3.注意系統調用get_pid()在內核中被定義乘sys_getpid()。這是linux中全部系統調用都應該遵照的命名規則,系統調用bar()在內核中也實現爲sys_bar()函數。

  • 系統調用號:在linux中,每一個系統調用被賦予一個系統調用號。這樣,經過這個獨一無二的號就能夠聯繫統調用。當用戶空間的進程執行一個系統調用的時候,這個系統調用號就用來指明究竟是要執行哪一個系統調用;進程不會說起系統調用的名稱;系統調用號至關重要,一旦分配就不能再有任何變動,不然編譯好的應用程序就會崩潰;內核記錄了系統調用表中的全部已註冊的系統調用的列表,存儲在sys_call_table中。 如:x86-64中,定義在arch/i386/kernel/syscall_64.c
  • 系統調用處理程序:由於用戶空間的程序沒法直接執行內核代碼。因此,應用程序應該以某種方式通知系統,告訴內核本身須要執行一個系統調用,但願系統切換到內核態,這樣內核就能夠表明應用程序在內核空間執行系統調用。
  • 通知內核的機制是靠軟中斷實現的:經過引起一個異常來促使系統切換到內核態去執行異常處理程序。此時的異常處理程序實際上就是系統調用處理程序。如:x86系統上預約義的軟中斷是中斷號128,經過int $0x80指令觸發該中斷。最近x86處理器增長了sysenter的指令。
  • x86上,系統調用號是經過eax寄存器傳遞給內核的:在陷入內核以前,用戶空間就把相應系統調用所對應的號放入eax中。這樣系統調用處理程序一旦運行就能夠從eax中獲得數據。
  • 參數傳遞:像傳遞系統調用號同樣,這些參數也是經過寄存器傳遞。給用戶空間的返回值也是經過寄存器傳遞。
  • 內核在執行系統調用的時候處於進程上下文。current指針指向當前任務,即引起系統調用的那個進程。在進程上下文中,內核能夠休眠,而且能夠被搶佔。

當系統調用返回時,控制權仍然在system_call()中,它最終會負責切換到用戶空間,並讓用戶進程繼續執行下去。

2.應用接口

  • x86架構:

  1.編寫一個系統調用;
2.在系統調用表的最後加入一個表項;
3 對於所支持的各類體系架構,系統調用號都必須定義域 asm/unistd.h中
4.系統調用必須被編譯進內核映象(不能被編譯成模塊)。這隻要把它放進kernel/下的一個相關文件中就能夠了,好比sys.c,它包含了各類各樣的系統調用。

  • arm架構

  1.編寫一個系統調用
  2.系統調用表內增長條目。 /arch/arm/kernel/calls.S內添加
3.應用實例

/*1.編寫foo()系統調用。咱們把它放入kernel/sys.c文件中。*/ #include <asm/page.h>

/* *sys_foo:每一個人喜歡的系統調用 *返回每一個進程的內核棧大小 */ asmlinkage long sys_foo(void) {   return THREAD_SIZE; } /*2.加入系統調用表 entry.s*/ ENTRY(sys_call_table) .long sys_restart_syscall /* 0 */ .long sys_exit .long sys_fork .long sys_read .long sys_write .long sys_open /* 5 */ ... .long sys_eventfd2 .long sys_epoll_create1 /* 330 */ .long sys_dup3 .long sys_pipe2 .long sys_inotify_init1 .long sys_preadv .long sys_pwritev /* 335 */ .long sys_rt_tgsigqueueinfo .long sys_perf_event_open .long sys_recvmmsg .long sys_setns .long sys_foo /338 myself syscall/

/* 3.定義系統調用號 asm/unistd.h */
#define __NR_foo 338
arm架構: 內核層

/*1.添加系統調用函數 在/kernel/sys.c內添加*/ asmlinkage long sys_foo(void) {   return THREAD_SIZE; } /*2.更新unistd.h 目錄:arch/arm/include/asm/unistd.h (不是這個) arch/arm/include/uapi/asm/unistd.h 注:只能添加在全部系統調用號的最後面*/
#define __NR_foo (__NR_SYSCALL_BASE+387)

/*3.系統調用表內增長條目 在/arch/arm/kernel/calls.S內 注:只能添加在全部CALL的最後面,而且與(2)的調用號相對應。不然必定使系統調用表混亂。*/ CALL(sys_foo)

用戶層測試:

#include <unistd.h> #include <sys/syscall.h> #include<stdio.h>

int main() {   int mem_size = 0;   mem_size = syscall(__NR_foo);   printf("the process mem size:%d bytes", mem_size);   return 0; }

二:虛擬文件系統
2.1 proc文件系統
1.簡介

  • 優秀博客:

https://www.ibm.com/developerworks/cn/linux/l-proc.html
https://blog.csdn.net/sty23122555/article/details/51638697
seq操做:
http://www.cnblogs.com/Wandererzj/archive/2012/04/16/2452209.html#commentform

  • 在linux系統中,proc文件系統被內核用於向用戶導出信息。「/proc」文件系統是一個虛擬文件系統,經過它能夠在Linux內核空間和用戶空間之間進行通訊。在/proc文件系統中,咱們能夠將對虛擬文件的讀寫做爲與內核中實體進行通訊的一種手段,與普通文件不一樣的是,這些虛擬文件的內容都是動態建立的。
  • 「/proc」下的絕大多數文件都是隻讀的,以顯示內核信息爲主。可是「/proc」下的文件也並非徹底只讀的,若節點可寫,還可用於必定的控制或配置目的。例如:/proc/sys/kernel/printk能夠改變printk()的打印級別。

2.應用接口

Linux 3.9以及以前的內核版本:
/*1.建立proc文件*/ //建立目錄 struct proc_dir_entry *proc_mkdir(const char *name, struct proc_dir_entry *parent); /*做用:用於建立"/proc"節點 參數: name:爲"/proc"節點的名稱 parent/base:爲父目錄的節點,若是爲NULL,則指"/proc"目錄。 返回值: create_proc_entry 的返回值是一個 proc_dir_entry 指針(或者爲 NULL,說明在 create 時發生了錯誤)。 而後就可使用這個返回的指針來配置這個虛擬文件的其餘參數,例如在對該文件執行讀操做時應該調用的函數。 注:當read()系統調用在"/proc"文件系統中執行時,它映象到一個數據產生函數,而不是一個數據獲取函數。 */ struct proc_dir_entry *create_proc_entry( const char *name, mode_t mode, struct proc_dir_entry *parent ); struct proc_dir_entry { ...... const struct file_operations *proc_fops; <==文件操做結構體 struct proc_dir_entry *next, *parent, *subdir; void *data; read_proc_t *read_proc; <==讀回調 write_proc_t *write_proc; <==寫回調 ...... }; /*2. 刪除proc文件/目錄*/ /*要從 /proc 中刪除一個文件,可使用 remove_proc_entry 函數。要使用這個函數,咱們須要提供文件名字符串, 以及這個文件在 /proc 文件系統中的位置(parent)。*/ void remove_dir_entry(const char *name, struct proc_dir_entry *parent); /*3. /proc節點的讀寫函數*/ /* proc文件其實是一個叫作proc_dir_entry的struct(定義在proc_fs.h),該struct中有int read_proc和int write_proc 兩個元素,要實現proc的文件的讀寫就要給這兩個元素賦值。但這裏不是簡單地將一個整數賦值過去就好了,須要實現兩個回調函數。 在用戶或應用程序訪問該proc文件時,就會調用這個函數,實現這個函數時只需將想要讓用戶看到的內容放入page便可。 在用戶或應用程序試圖寫入該proc文件時,就會調用這個函數,實現這個函數時須要接收用戶寫入的數據(buff參數)。 */ static int (*proc_read)(char *page, char **start, off_t off, int count, int *eof, void *data); static int proc_write_foobar(struct file *file, const char *buffer, unsigned long count, void *data);

Linux 3.10及之後的內核版本:
「/proc」的內核API和實現架構變動較大,create_proc_entry()、create_proc_read_entry()之類的API都被刪除了,取而代之的是直接使用proc_create()、proc_create_data() API。同時,也再也不存在read_proc()、write_proc()之類的針對proc_dir_entry的成員函數了,而是直接把file_operations結構體的指針傳入proc_create()或者proc_create_data()函數中。

static inline struct proc_dir_entry *proc_create(         const char *name, umode_t mode, struct proc_dir_entry *parent,         const struct file_operations *proc_fops) {   return proc_create_data(name, mode, parent, proc_fops, NULL); } struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,                         struct proc_dir_entry *parent,                         const struct file_operations *proc_fops,                         void *data)

3.實例
Linux 3.9以及以前的內核版本

#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/proc_fs.h>

static unsigned int variable; static struct proc_dir_entry *test_dir, *test_entry; static int test_proc_read(char *buf, char **start, off_t off, int count,int *eof, void *data) {   unsigned int *ptr_var = data;   return sprintf(buf, "%u\n", *ptr_var); } static int test_proc_write(struct file *file, const char *buffer,unsigned long count, void *data) {   unsigned int *ptr_var = data;   *ptr_var = simple_strtoul(buffer, NULL, 10);   return count; } static __init int test_proc_init(void) {   test_dir = proc_mkdir("test_dir", NULL);   if (test_dir) {     test_entry = create_proc_entry("test_rw", 0666, test_dir);     if (test_entry) {        test_entry->nlink = 1;        test_entry->data = &variable;        test_entry->read_proc = test_proc_read;        test_entry->write_proc = test_proc_write;        return 0;     }   }   return -ENOMEM; } module_init(test_proc_init); static __exit void test_proc_cleanup(void) {   remove_proc_entry("test_rw", test_dir);   remove_proc_entry("test_dir", NULL); } module_exit(test_proc_cleanup); MODULE_AUTHOR("Barry Song <baohua@kernel.org>"); MODULE_DESCRIPTION("proc example"); MODULE_LICENSE("GPL v2");

Linux 3.10及之後的內核版本:

#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/version.h> #include <linux/proc_fs.h> #include <linux/seq_file.h>

static unsigned int variable; static struct proc_dir_entry *test_dir, *test_entry; #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
//other code
#else
static int test_proc_show(struct seq_file *seq, void *v) {   unsigned int *ptr_var = seq->private;   seq_printf(seq, "%u\n", *ptr_var);   return 0; } static ssize_t test_proc_write(struct file *file, const char __user *buffer,size_t count, loff_t *ppos) {   struct seq_file *seq = file->private_data;   unsigned int *ptr_var = seq->private;   *ptr_var = simple_strtoul(buffer, NULL, 10);   return count; } static int test_proc_open(struct inode *inode, struct file *file) {   return single_open(file, test_proc_show, PDE_DATA(inode)); } static const struct file_operations test_proc_fops = {   .owner = THIS_MODULE,   .open = test_proc_open,   .read = seq_read,   .write = test_proc_write,   .llseek = seq_lseek,   .release = single_release, }; #endif

static __init int test_proc_init(void) {   test_dir = proc_mkdir("test_dir", NULL);   if (test_dir) {     #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 10, 0)
      //other code
    #else       test_entry = proc_create_data("test_rw", 0666, test_dir, &test_proc_fops, &variable);     if (test_entry) {       return 0;     }   #endif   }   return -ENOMEM; } module_init(test_proc_init); static __exit void test_proc_cleanup(void) {   remove_proc_entry("test_rw", test_dir);   remove_proc_entry("test_dir", NULL); } module_exit(test_proc_cleanup); MODULE_AUTHOR("Barry Song <baohua@kernel.org>"); MODULE_DESCRIPTION("proc example"); MODULE_LICENSE("GPL v2");

Makefile:

ifneq ($(KERNELRELEASE),) obj-m := proc_test.o else PWD := $(shell pwd) KDIR := /lib/modules/$(shell uname -r)/build all: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions modules.order Module.symvers endif

2.2:sysfs文件系統
1.簡介

資料:
http://www.wowotech.net/linux_kenrel/dm_sysfs.html

  • sysfs被當作是與proc、devfs和devpty同類別的文件系統,該文件系統是一個虛擬的文件系統,它能夠產生一個包括全部系統硬件的層級試圖,與提供進程和狀態信息的proc文件系統十分相似。
  • sysfs把鏈接在系統上的設備和總線組織成爲一個分級的文件,它們能夠有用戶空間存取,向用戶空間導出內核數據結構以及他們的屬性。sysfs的一個目的就是展現設備驅動模型中各組件的層次關係,其頂級目錄包括block、bus、dev、devices、class、fs、kernel、prwer和firmware等。

    block:包含全部的塊設備。
    devices:包含系統全部的設備,並根據設備掛接的總線類型組織成層次結構。
    bus:包含系統中全部的總線類型。
    class:包含系統中的設備類型(如網卡設備、聲卡設備、輸入設備等)。

  • 總線、驅動和設備最終都會落實爲sysfs中的一個目錄,進一步追蹤代碼會發現,它們實際上均可以認爲是kobject的派生類,kobject可看作是全部總線、設備和驅動的抽象基類,1個kobject對應sysfs中的一個目錄。

2.應用接口

  • 總線、設備和驅動中的各個attribute則直接落實爲sysfs中的一個文件,attribute會伴隨着show()和store()這兩個函數,分別用於讀寫該attribute對應的sysfs文件。
  • sysfs中的目錄來源於bus_type、device_driver、device,而目錄中的文件則來源於attribute。
/* _name:名稱,也就是將在sys fs中生成的文件名稱。 _mode:上述文件的訪問權限,與普通文件相同,UGO的格式。 _show:顯示函數,cat該文件時,此函數被調用。 _store:寫函數,echo內容到該文件時,此函數被調用。 */

/*1.driver: sysfs interface for exporting driver attributes */
struct driver_attribute {   struct attribute attr;   ssize_t (*show)(struct device_driver *driver, char *buf);   ssize_t (*store)(struct device_driver *driver, const char *buf,             size_t count); }; #define DRIVER_ATTR(_name, _mode, _show, _store) \
struct driver_attribute driver_attr_##_name = __ATTR(_name, _mode, _show, _store) #define DRIVER_ATTR_RW(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_RW(_name) #define DRIVER_ATTR_RO(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_RO(_name) #define DRIVER_ATTR_WO(_name) \
struct driver_attribute driver_attr_##_name = __ATTR_WO(_name) /*2.devices: interface for exporting device attributes */
struct device_attribute {   struct attribute attr;   ssize_t (*show)(struct device *dev, struct device_attribute *attr,             char *buf);   ssize_t (*store)(struct device *dev, struct device_attribute *attr,               const char *buf, size_t count); }; ssize_t device_show_ulong(struct device *dev, struct device_attribute *attr,                 char *buf); ssize_t device_store_ulong(struct device *dev, struct device_attribute *attr,                 const char *buf, size_t count); ssize_t device_show_int(struct device *dev, struct device_attribute *attr,                 char *buf); ssize_t device_store_int(struct device *dev, struct device_attribute *attr,                 const char *buf, size_t count); ssize_t device_show_bool(struct device *dev, struct device_attribute *attr,                 char *buf); ssize_t device_store_bool(struct device *dev, struct device_attribute *attr,                 const char *buf, size_t count); #define DEVICE_ATTR(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = __ATTR(_name, _mode, _show, _store) #define DEVICE_ATTR_RW(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RW(_name) #define DEVICE_ATTR_RO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RO(_name) #define DEVICE_ATTR_WO(_name) \
struct device_attribute dev_attr_##_name = __ATTR_WO(_name) #define DEVICE_ULONG_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \ { __ATTR(_name, _mode, device_show_ulong, device_store_ulong), &(_var) } #define DEVICE_INT_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \ { __ATTR(_name, _mode, device_show_int, device_store_int), &(_var) } #define DEVICE_BOOL_ATTR(_name, _mode, _var) \
struct dev_ext_attribute dev_attr_##_name = \ { __ATTR(_name, _mode, device_show_bool, device_store_bool), &(_var) } #define DEVICE_ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) \
struct device_attribute dev_attr_##_name = \ __ATTR_IGNORE_LOCKDEP(_name, _mode, _show, _store) /*3.bus*/
struct bus_attribute { struct attribute attr; ssize_t (*show)(struct bus_type *bus, char *buf); ssize_t (*store)(struct bus_type *bus, const char *buf, size_t count); }; #define BUS_ATTR(_name, _mode, _show, _store) \
struct bus_attribute bus_attr_##_name = __ATTR(_name, _mode, _show, _store) #define BUS_ATTR_RW(_name) \
struct bus_attribute bus_attr_##_name = __ATTR_RW(_name) #define BUS_ATTR_RO(_name) \
struct bus_attribute bus_attr_##_name = __ATTR_RO(_name)

3.實例

/*1.定義接口函數*/
static ssize_t bma253_state_show(struct device *dev, struct device_attribute *attr, char *buf) {     struct bma253_data *pdata = &bma253_dev;     int enable = 0;     enable = atomic_read(&pdata->work_mode);     return sprintf(buf, "%d\n", enable); } static ssize_t bma253_state_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) {     struct bma253_data *pdata = &bma253_dev;     int ret;     unsigned long enable;     if(kstrtoul(buf, 10, &enable) < 0)     return -EINVAL;     ret = bma253_change_mode(pdata, enable);     if (!ret) {         atomic_set(&pdata->work_mode, enable);         printk(KERN_INFO "power status =0x%x\n", (int)enable);     }     return count; } /*2.快捷建立*/
static DEVICE_ATTR(enable, 0666, bma253_state_show, bma253_state_store); /*3.*/
static struct attribute *bma253_attributes[] = {     &dev_attr_enable.attr,     NULL }; /*4.*/
static const struct attribute_group bma253_attr_group = {     .attrs = bma253_attributes, }; /*5.在probe中調用,建立sys調試文件*/ result = sysfs_create_group(&bma253_device.this_device->kobj, &bma253_attr_group); if (result) {     printk(KERN_ERR "create device file failed!\n");     result = -EINVAL;     goto err_create_sysfs; }

完整實例:

#include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/ioport.h> #include <linux/errno.h> #include <linux/workqueue.h> #include <linux/platform_device.h> #include <linux/types.h> #include <linux/io.h> typedef struct {     int num; } hello_priv; /*1.定義接口函數*/
static ssize_t hello_power_store(struct device *dev,struct device_attribute *attr,const char *buf, size_t count) {     hello_priv* prv = dev_get_drvdata(dev);     unsigned long power;     if(kstrtoul(buf, 10, &power) < 0)         return -EINVAL;        prv->num = (int)power;     return count; } static ssize_t hello_power_show(struct device *dev, struct device_attribute *attr, char *buf) {     hello_priv* prv = dev_get_drvdata(dev);     int power = 0;     power = prv->num;     return sprintf(buf, "%d\n", power); } /*2. 設置DEVICE_ATTR: 名稱power_on必須與attribute中的名稱對應*/
static DEVICE_ATTR(power_state, S_IRUGO | S_IWUSR, hello_power_show, hello_power_store); //show接口爲空,只有store接口,即只支持寫不技持讀


/*3. 設置DEVICE_ATTR*/
static struct attribute *hello_attributes[] = {     &dev_attr_power_state.attr, //dev_attr_****.attr中間的××××必須與DEVICE_ATTR中的名稱對應
    NULL }; /*4. 封裝到attribute_group中*/
static const struct attribute_group hello_attr_group = {     .attrs = hello_attributes, }; static int __init hello_probe(struct platform_device *pdev) {     int rc;     hello_priv* prv;     prv = devm_kzalloc(&pdev->dev, sizeof(hello_priv), GFP_KERNEL);     if (!prv) {         dev_err(&pdev->dev, "failed to allocate hello\n");         return -ENOMEM;     }     prv->num = 2;     platform_set_drvdata(pdev, prv);     /*5.調用sysfs_create_group建立/sys下的文件*/     rc = sysfs_create_group(&pdev->dev.kobj, &hello_attr_group);     if (rc) {         dev_err(&pdev->dev,"failed to create hello sysfs group\n");         goto fail;     }     return 0; fail:     return rc; } struct platform_driver hello_driver = {     .driver = {         .name = "hello",         .owner = THIS_MODULE,     },     .probe = hello_probe,
};
static struct platform_device hello_device = {     .name = "hello",     .id = -1, }; static int __init hello_init(void) {     int ret;     ret = platform_device_register(&hello_device);     if (ret != 0)     printk(KERN_NOTICE "cong: %s:%s[%d]: error", __FILE__,__FUNCTION__, __LINE__);     ret = platform_driver_register(&hello_driver);     return ret; } static void __init hello_exit(void) {     platform_driver_unregister(&hello_driver); } module_init(hello_init); module_exit(hello_exit); MODULE_DESCRIPTION("Hellow test Driver"); MODULE_AUTHOR("vec"); MODULE_LICENSE("GPL");

2.3 debugfs文件繫系統
1.簡介

  • 資料:

https://blog.csdn.net/luckywang1103/article/details/26809843
2.應用接口
3.實例

三:ioctl接口
1.簡介
優秀博客:
http://blog.chinaunix.net/uid-25014876-id-59419.html
https://blog.csdn.net/zifehng/article/details/59576539

  • 一個字符設備驅動一般會實現常規的打開、關閉、讀、寫等功能,但在一些細分的情境下,若是須要擴展新的功能,一般以增設ioctl()命令的方式實現,其做用相似於「拾遺補漏」。

2.應用接口
用戶空間:

#include <sys/ioctl.h>

/* 參數: fd:文件描述符。 cmd:交互協議,設備驅動將根據cmd執行對應操做。 ...:可變參數arg,依賴cmd指定長度以及類型。 返回值: ioctl()執行成功時返回0,失敗則返回-1並設置全局變量errorno值 注: 在實際應用中,ioctl出錯時的errorno大部分是ENOTTY(error not a typewriter),顧名思義, 即第一個參數fd指向的不是一個字符設備,不支持ioctl操做,這時候應該檢查前面的open函數是否 出錯或者設備路徑是否正確。 */

int ioctl(int fd, int cmd, ...) ;

內核空間:

  • 在新版內核中,unlocked_ioctl()與compat_ioctl()取代了ioctl()。unlocked_ioctl(),顧名思義,應該在無大內核鎖(BKL)的狀況下調用;compat_ioctl(),compat全稱compatible(兼容的),主要目的是爲64位系統提供32位ioctl的兼容方法,也是在無大內核鎖的狀況下調用。

注:
在字符設備驅動開發中,通常狀況下只要實現unlocked_ioctl()便可,由於在vfs層的代碼是直接調用unlocked_ioctl()。

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

ioctl命令,用戶與驅動之間的協議

type設備類型字段:爲一個「幻數」,能夠是0~0xff的值,內核中的ioctl-number.txt給出了一些推薦的和已經被使用的「幻數」,新設備驅動定義「幻數」的時候要避免與其衝突。
nr序列號字段:8位
dir方向字段:表示數據傳送方向,可能的值是 _IOC_NONE(無數據傳輸)、_IOC_READ(讀)、_IOC_WRITE(寫)、_IOC_READ|_IOC_WRITE(雙向)。
size數據尺寸字段:表示涉及的用戶數據的大小,這個成員的寬度依賴於體系結構,一般是13或者14位。

宏-輔助生成命令
做用:根據傳入的type(設備類型字段)、nr(序列號字段)、size(數據長度字段)和宏名隱含的方向字段移位組合生成命令碼。

#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

/*_IO、_IOR等使用的_IOC宏*/
#define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT)) eg: #define GLOBALMEM_MAGIC 'g'
#define MEM_CLEAR _IO(GLOBALMEM_MAGIC, 0)

3.實例
公用頭文件
ioctl-test.h,用戶空間和內核空間共用的頭文件,包含ioctl命令及相關宏定義,能夠理解爲一份「協議」文件。

// ioctl-test.h
 #ifndef __IOCTL_TEST_H__ #define __IOCTL_TEST_H__ #include <linux/ioctl.h> // 內核空間 // #include <sys/ioctl.h> // 用戶空間

/* 定義設備類型 */
#define IOC_MAGIC 'c'

/* 初始化設備 */
#define IOCINIT _IO(IOC_MAGIC, 0)

/* 讀寄存器 */
#define IOCGREG _IOW(IOC_MAGIC, 1, int)

/* 寫寄存器 */
#define IOCWREG _IOR(IOC_MAGIC, 2, int)

#define IOC_MAXNR 3

struct msg {     int addr;     unsigned int data; }; #endif

內核空間:

// ioctl-test-driver.c
...... static const struct file_operations fops = {     .owner = THIS_MODULE,     .open = test_open,     .release = test_close,     .read = test_read,     .write = etst_write,     .unlocked_ioctl = test_ioctl, }; ...... static long test_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { //printk("[%s]\n", __func__);

    int ret;     struct msg my_msg;     /* 檢查設備類型 */
    if (_IOC_TYPE(cmd) != IOC_MAGIC) {         pr_err("[%s] command type [%c] error!\n", __func__, _IOC_TYPE(cmd));         return -ENOTTY;     }     /* 檢查序數 */
    if (_IOC_NR(cmd) > IOC_MAXNR) {         pr_err("[%s] command numer [%d] exceeded!\n",         __func__, _IOC_NR(cmd));         return -ENOTTY;     }     /* 檢查訪問模式 */
    if (_IOC_DIR(cmd) & _IOC_READ)         ret= !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));     else if (_IOC_DIR(cmd) & _IOC_WRITE)       ret= !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));     if (ret)       return -EFAULT;     switch(cmd) {       /* 初始化設備 */
      case IOCINIT:       init();       break;       /* 讀寄存器 */
      case IOCGREG:         ret = copy_from_user(&msg, (struct msg __user *)arg, sizeof(my_msg));         if (ret)           return -EFAULT;         msg->data = read_reg(msg->addr);         ret = copy_to_user((struct msg __user *)arg, &msg, sizeof(my_msg));         if (ret)           return -EFAULT;         break;       /* 寫寄存器 */
      case IOCWREG:           ret = copy_from_user(&msg, (struct msg __user *)arg, sizeof(my_msg));           if (ret)             return -EFAULT;           write_reg(msg->addr, msg->data);           break;       default:           return -ENOTTY;   }   return 0; }

用戶空間:

// ioctl-test.c
 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <sys/ioctl.h> #include "ioctl-test.h"

int main(int argc, char **argv) {     int fd;     int ret;     struct msg my_msg;     fd = open("/dev/ioctl-test", O_RDWR);     if (fd < 0) {         perror("open");         exit(-2);     }     /* 初始化設備 */     ret = ioctl(fd, IOCINIT);     if (ret) {       perror("ioctl init:");       exit(-3);     }     /* 往寄存器0x01寫入數據0xef */     memset(&my_msg, 0, sizeof(my_msg));     my_msg.addr = 0x01;     my_msg.data = 0xef;     ret = ioctl(fd, IOCWREG, &my_msg);     if (ret) {       perror("ioctl read:");       exit(-4);     }     /* 讀寄存器0x01 */     memset(&my_msg, 0, sizeof(my_msg));     my_msg.addr = 0x01;     ret = ioctl(fd, IOCGREG, &my_msg);     if (ret) {       perror("ioctl write");       exit(-5);     }     printf("read: %#x\n", my_msg.data);     return 0; }

四. netlink
1.簡介
借鑑博客:
https://blog.csdn.net/stone8761/article/details/72780863

  • netlink使用32位端口尋址,稱爲pid(與進程號沒有關係),其中內核的pid地址爲0,。netlink主要特性以下:
  1. 支持全雙工、異步通訊(固然同步也支持)
  2. 用戶空間可以使用標準的BSD socket接口(但netlink並無屏蔽掉協議包的構造與解析過程,推薦使用libnl等第三方庫)
  3. 在內核空間使用專用的內核API接口
  4. 支持多播(所以支持「總線」式通訊,可實現消息訂閱)
  5. 在內核端可用於進程上下文與中斷上下文

2.應用接口
內核層操做

/*1.建立socket 參數: net: 通常直接填&init_net unit:協議類型,可自定義,如#define NETLINK_TEST 25 cfg:配置結構 */
static inline struct sock * 
  netlink_kernel_create(
struct net *net, int unit, struct netlink_kernel_cfg *cfg); /*2.單播發送接口 參數: ssk:爲函數 netlink_kernel_create()返回的socket。 skb:存放消息,它的data字段指向要發送的netlink消息結構,而 skb的控制塊保存了消息的地址信息,宏NETLINK_CB(skb)就用於方便設置該控制塊。 portid:pid端口。 nonblock:表示該函數是否爲非阻塞,若是爲1,該函數將在沒有接收緩存可利用時當即返回;而若是爲0,該函數在沒有接收緩存可利用定時睡眠。 */ extern int netlink_unicast(struct sock *ssk, struct sk_buff *skb, __u32 portid, int nonblock); /*3.多播發送接口 參數: group:接收消息的多播組,該參數的每個位表明一個多播組,所以若是發送給多個多播組; allocation:內存分配類型,通常地爲GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不能夠睡眠),而GFP_KERNEL用於非原子上下文。 */ extern int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid,                   __u32 group, gfp_t allocation); /*4.釋放socket 參數: */ extern void netlink_kernel_release(struct sock *sk);

用戶層操做

/*1.建立socket 參數: nlmsghdr結構常見操做: NLMSG_SPACE(len): 將len加上nlmsghdr頭長度,並按4字節對齊; NLMSG_DATA(nlh): 返回數據區首地址; */
int netlink_create_socket(void) {     //create a socket 
    return socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); } /*2.bind 參數: */
int netlink_bind(int sock_fd) {     struct sockaddr_nl addr;     memset(&addr, 0, sizeof(struct sockaddr_nl));     addr.nl_family = AF_NETLINK;     addr.nl_pid = TEST_PID;     addr.nl_groups = 0;     return bind(sock_fd, (struct sockaddr *)&addr, sizeof(struct sockaddr_nl)); } /*3.發送接收*/
//使用sendmsg、recvmsg發送接收數據
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); //使用sendto、recvfrom發送接收數據
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,           const struct sockaddr *dest_addr, socklen_t addrlen); ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,           struct sockaddr *src_addr, socklen_t *addrlen);

 

3.實例

#include <stdio.h> #include <stdlib.h> #include <sys/socket.h> #include <string.h> #include <linux/netlink.h> #include <stdint.h> #include <unistd.h> #include <errno.h> #include <sys/poll.h>

static void die(char *s) {     write(2, s, strlen(s));     exit(1); } int main(int argc, char *argv[]) {     struct sockaddr_nl nls;     struct pollfd pfd;     char buf[512];     //open hotplug event netlink socket
    memset(&nls, 0, sizeof(struct sockaddr_nl));     nls.nl_family = AF_NETLINK;     nls.nl_pid = getpid();     nls.nl_groups = -1;     pfd.events = POLLIN;     pfd.fd = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);     if (pfd.fd == -1)     die("Not root\n");     //listen to netlink socket
    if (bind(pfd.fd, (void *)&nls, sizeof(struct sockaddr_nl)))     die("Bind failed\n");     while(-1 != poll(&pfd, 1, -1)) {         int i, len = recv(pfd.fd, buf, sizeof(buf), MSG_DONTWAIT);         if (len == -1)         die("recv\n");            //print the data to stdout
        i = 0;         while (i < len) {           printf("%s\n", buf + i);           i += strlen(buf + i) + 1;         }     }     die("poll\n");     return 0; }

 

編譯:gcc netlink_test.c -o netlink_test————————————————

相關文章
相關標籤/搜索