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


方法列表:

1.系統調用

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

3.ioctl接口

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

一:系統調用
1.簡介

  • 優秀博客:

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

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

          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
————————————————


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM