• 参考文章: 一个块设备驱动 参考文章中有一些函数在如今的内核版本中已经被删除了,下面的是调试好的不适用请求队列的简单块设备驱动。 上传了完整项目文档说明和代码:操作系统课程设计-简单字符设备和块...

    参考文章: 写一个块设备驱动

    参考文章中有一些函数在如今的内核版本中已经被删除了,下面写的是调试好的不适用请求队列的简单块设备驱动。

    上传了完整项目文档说明和代码:操作系统课程设计-简单字符设备和块设备驱动程序

    3.1 定义

    是指可以从设备的任意位置读取一定长度的数据设备。块设备如硬盘、磁盘、U盘和SD卡等存储设备。

    3.2 实现步骤

    1驱动代码

    定义一个全局的块设备

    static struct gendisk *blodev;

    为这个块设备申请资源、配置、加入系统

    blodev = alloc_disk(1);

     

            strcpy(blodev->disk_name,MY_BLOCK_NAME);

            blodev->major=MY_DEVICE_MAJOR;

            blodev->first_minor=0;

            blodev->fops=&blodev_fops;

            blodev->queue=blodev_queue;

            set_capacity(blodev,MY_BLOCK_CAPACITY>>9);

     

    add_disk(blodev);

     

    到此整个驱动的结构已经出来,剩下的便是添加配置块设备中所需要的函数和数据,配置块设备的代码有六行,下面逐行进行填充。

    第一行:需要设置块设备的名称,添加宏定义:

    #define MY_BLOCK_NAME "demo2_blodev"

    第二三行:设置主从驱动号,这里使用的是操作系统已存在的驱动号,添加宏定义:

    #define MY_DEVICE_MAJOR COMPAQ_SMART2_MAJOR

    第四行:设置块设备的文件操作函数,添加一个结构体:

    struct block_device_operations blodev_fops={

            .owner=THIS_MODULE

    };

    第五行:设置io请求队列,请求队列的处理函数便是这个驱动的核心内容,这里采用的无队列和全局变量存储的方式。

    static struct request_queue *blodev_queue; //定义请求队列

    unsigned char blodev_data[MY_BLOCK_CAPACITY]; //全局数据存储数组

    //io请求的处理函数

    static unsigned int do_request(struct request_queue *q,struct bio * bio);

    //队列资源申请

    blodev_queue=blk_alloc_queue(GFP_KERNEL);

    //请求队列和对应的处理函数绑定

    blk_queue_make_request(blodev_queue,do_request);

     

    第六行,设置块设备的磁盘容量,添加宏定义:

    #define MY_BLOCK_CAPACITY (512*1024) //512k的磁盘空间

     

    最后添加驱动模块的必要部分,就完成了整个驱动的编写。

    static void __exit my_exit(void);

    static int __init my_init(void);

     

    module_init(my_init);

    module_exit(my_exit);

     

    MODULE_LICENSE("GPL");

    MODULE_AUTHOR("guoz");

    MODULE_DESCRIPTION("this is a ostest demo2");

     

    2 Makefile 文件

    ifneq ($(KERNELRELEASE),)

    obj-m := demo2.o

    else

    PWD := $(shell pwd)

    KVER := $(shell uname -r)

    KDIR := /lib/modules/$(KVER)/build

    all:

    $(MAKE) -C $(KDIR) M=$(PWD) modules

    clean:

    rm -fr .*.cmd *.o *.mod.c *.ko .tmp_versions modules.* Module.*

    endif

     

    3.3 调试 (这部分本是用图片展示,这里就简单地写出命令)

    1检查文件

    $ ls

    demo2.c Makefille

    2编译和动态添加驱动

    $ make

    $ insmod demo2.ko

    3 查看驱动模块

    $ lsmod

    4 添加块设备文件

    $ mknod /dev/demo2 b 72 0

    $ ll /dev/demo2

    5 为块设备建立文件系统

    $ mkfs.ext3 /dev/demo2

    6 挂载设备

    $ mkdir -p /mnt/demo2

    $ mount /dev/demo2 /mnt/demo2

    $ mount

    7 写入、读取

    $ cp Makefile /mnt/demo2/

    $ cat /mnt/demo2/Makefil

    8 查看文件系统状态

    $ df

     

    1. 总结开发驱动遇到的问题

    1 在编写Makefile 文件中,只有在写命令时,该行的开头才能用tab符号。我习惯在vim编辑器中设置tab键等于4个空格,这个会导致Makefile文件make时报错。解决办法有两种,一是在vim编辑器中取消tab键的设置,二是使用 cat <EOF >Makefile,但这个我只要centos用过,写一些简短的配置文件很方便。

    2 在字符设备中,常常会用到 copy_to_user & copy_from_user 这两个函数,但在网上一些教程中,添加的头文件是 #include<asm/uaccess.h> ,这是在linux内核2.6 中有的。我最后查到 现在要使用这两个函数需要添加 #include<linux/uaccess.h> 否则就会报函数申明不存在的错误。

    3 在块设备驱动中,我们可能会在一些资料上看到使用 elv_next_request()等一些函数来循环处理io请求队列,但那个已经是比较久之前使用的函数,现在已经被删除了,而改用blk_fetch_request()等一些新的函数来处理请求队列。

    4 作为一个刚接触驱动的,我觉得比较快的学习方式是在对要编写驱动的一些概念有了一些了解后,可以先找一个完整的驱动项目调试完成,然后,不断简化,直到只剩下一些删了就报错的代码。这时候分析弄懂这段代码,采用自顶向下的方法按照自己的想法去填充这个代码骨架。

    展开全文
  • 本文基于Linux-4.14 文件系统框架 Linux内核的文件系统框架图如下所示: gendisk对象 Linux中用一个gendisk对象结构体表示一个磁盘分区,这个结构体对象中会包含该分区对应的设备文件的主设备号,次设备号,以及...

    本文基于Linux-4.14

    文件系统框架

    Linux内核的文件系统框架图如下所示:
    文件系统框架

    gendisk对象

    Linux中用一个gendisk对象结构体表示一个磁盘分区,这个结构体对象中会包含该分区对应的设备文件的主设备号,次设备号,以及对应的gendisk->fops操作函数,这个块设备操作方法结构体如下所示:

    struct block_device_operations {
        int (*open) (struct block_device *, fmode_t);
        void (*release) (struct gendisk *, fmode_t);
        int (*rw_page)(struct block_device *, sector_t, struct page *, bool);
        int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
        int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
        unsigned int (*check_events) (struct gendisk *disk,
                          unsigned int clearing);
        /* ->media_changed() is DEPRECATED, use ->check_events() instead */
        int (*media_changed) (struct gendisk *);
        void (*unlock_native_capacity) (struct gendisk *);
        int (*revalidate_disk) (struct gendisk *);
        int (*getgeo)(struct block_device *, struct hd_geometry *);
        /* this callback is with swap_lock and sometimes page table lock held */
        void (*swap_slot_free_notify) (struct block_device *, unsigned long);
        struct module *owner;
        const struct pr_ops *pr_ops;
    };
    
    

    和字节设备cdev类似,cdev有用自己的操作函数file_operations; gendisk(block device)拥有自己的block_device_operations。实现这个结构体并且注册后,会在/dev目录下产生一个设备文件。对于驱动的实现需要使用如下三个关键API:

    block/genhd.c:

    struct gendisk *alloc_disk(int minors);//申请gendisk对象
    void add_disk(struct gendisk *disk); //注册gendisk对象
    void del_gendisk(struct gendisk *gp);//注销gendisk对象
    

    此时你可能会产生一个疑问,上面的fops中并没有read、write系统调用的定义,那么块设备是怎么进行读写的呢?
    实际上块设备的访问方式有两种:

    • 第一种是通过/dev下的设备文件来访问
    • 第二种就是通过文件系统的read、write来访问

    一般第一种方式我们大多是用来ioctl操作,获取块设备的属性;第二种操作才是用来读写访问的。通过文件系统来访问块设备,文件系统的最底层和块设备的关联的就是request_queue,每一个gendisk对象都有一个request_queue对象:gendisk->request_queue。它是文件系统和底层块设备驱动之间IO的桥梁,在下一个小节介绍。

    request_queue对象

    每个gendisk对象都会绑定一个request_queue对象,通过gendisk->request_queue可以访问对应的request_queue。文件系统通过把submit_bio请求发送给request_queue,底层块设备从request_queue读取request进行读写操作。在这一套运行机制下,底层设备块驱动只需要注册一个回调到对应的request_queue中,然后从request_queue中接收请求并处理,即可达到读写的目的。相关的操作API如下:

    block/blk-core.c:

    struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)  //注册一个request请求处理回调函数到request_queue中
    {
        return blk_init_queue_node(rfn, lock, NUMA_NO_NODE);
    }
    EXPORT_SYMBOL(blk_init_queue);
    
    
    
    struct request_queue *
    blk_init_queue_node(request_fn_proc *rfn, spinlock_t *lock, int node_id)
    {
        struct request_queue *q;
    
        q = blk_alloc_queue_node(GFP_KERNEL, node_id);
        if (!q)
            return NULL;
    
        q->request_fn = rfn;
        if (lock)
            q->queue_lock = lock;
    
        if (blk_init_allocated_queue(q) < 0) {
            blk_cleanup_queue(q);
            return NULL;
        }
    
        return q;
    }
    EXPORT_SYMBOL(blk_init_queue_node);
    

    除了注册回调函数,block驱动层还需要维护request_queue的读取操作,一般会用一个线程去读取request_queue中的request请求,在这个内核线程中使用如下通用相关API:

    struct request *blk_fetch_request(struct request_queue *q); //获取并处理一个request
    void blk_start_queue(struct request_queue *q);  //开始一个request queue
    void blk_stop_queue(struct request_queue *q) ;  //停止一个request queue
    
    

    mmc驱动架构(块设备的一种)

    前面介绍了块设备的实现框架层,那么mmc驱动属于块设备的一种,我们通过介绍它来讲解gendisk是如何生成的,以及request_queue是如何维护的。
    mmc驱动框架代码目录drivers/mmc/core,其中mmc设备注册gendisk的操作:

     static struct mmc_blk_data *mmc_blk_alloc_req(struct mmc_card *card,
                               struct device *parent,
                               sector_t size,
                               bool default_ro,
                               const char *subname,
                               int area_type)
     {
    
    ...... 
     md->disk = alloc_disk(perdev_minors);
     if (md->disk == NULL) {
         ret = -ENOMEM;
         goto err_kfree;
     }
    
    
     spin_lock_init(&md->lock);
     INIT_LIST_HEAD(&md->part);
     md->usage = 1;
    
    
     ret = mmc_init_queue(&md->queue, card, &md->lock, subname, area_type); //初始化绑定在该块设备上的request_queue,实现在后面介绍
     if (ret)
         goto err_putdisk;
    
     md->queue.blkdata = md;
    
     md->disk->major = MMC_BLOCK_MAJOR;
     md->disk->first_minor = devidx * perdev_minors;
     md->disk->fops = &mmc_bdops;
     md->disk->private_data = md;
     md->disk->queue = md->queue.queue;
    
    

    mmc设备驱动框架注册request_queue初始化和处理函数的接口:

    int mmc_init_queue(struct mmc_queue *mq, struct mmc_card *card,
    
               spinlock_t *lock, const char *subname, int area_type)
    {
    ......
    mq->queue = blk_alloc_queue(GFP_KERNEL); //申请一个request_queue
    if (!mq->queue)
        return -ENOMEM;
    
    mq->queue->queue_lock = lock;
    mq->queue->request_fn = mmc_request_fn;  //关键实现:设置request处理的回调函数
    mq->queue->init_rq_fn = mmc_init_request;
    mq->queue->exit_rq_fn = mmc_exit_request;
    mq->queue->cmd_size = sizeof(struct mmc_queue_req);
    mq->queue->queuedata = mq;
    mq->qcnt = 0;
    
    ret = blk_init_allocated_queue(mq->queue);  //初始化request_queue,其中会把对应的回调函数使能
    if (ret) {
        blk_cleanup_queue(mq->queue);
        return ret;
    }
    
    blk_queue_prep_rq(mq->queue, mmc_prep_request);
    queue_flag_set_unlocked(QUEUE_FLAG_NONROT, mq->queue);
    queue_flag_clear_unlocked(QUEUE_FLAG_ADD_RANDOM, mq->queue);
    
    if (mmc_can_erase(card))
        mmc_queue_setup_discard(mq->queue, card);
    
    
    blk_queue_bounce_limit(mq->queue, limit);
    blk_queue_max_hw_sectors(mq->queue,
        min(host->max_blk_count, host->max_req_size / 512));
    blk_queue_max_segments(mq->queue, host->max_segs);
    blk_queue_max_segment_size(mq->queue, host->max_seg_size);
    if (host->inlinecrypt_support)
        queue_flag_set_unlocked(QUEUE_FLAG_INLINECRYPT, mq->queue);
    
    

    除了完成上面的初始化和注册功能,mmc框架还是需要实现request_queue的管理,上面介绍已经通过request_fn注册了回调函数,那么需要开启一个线程定期读取并且触发request的处理:

     static int mmc_queue_thread(void *d)
     {
         struct mmc_queue *mq = d;
         struct request_queue *q = mq->queue;
         struct mmc_context_info *cntx = &mq->card->host->context_info;
         struct sched_param scheduler_params = {0};
     
         scheduler_params.sched_priority = 1;
     
         sched_setscheduler(current, SCHED_FIFO, &scheduler_params);
     
         current->flags |= PF_MEMALLOC;
     
         down(&mq->thread_sem);
         do {
             struct request *req;
     
             spin_lock_irq(q->queue_lock);
             set_current_state(TASK_INTERRUPTIBLE);
             req = blk_fetch_request(q);       //在这里会调用上面注册的回调函数request_fn,为什么不直接在这里调用request_fn?
                                                //因为注册到框架的request_fn函数,除了block驱动层会用,block框架层也需要用。
             mq->asleep = false;
             cntx->is_waiting_last_req = false;
             cntx->is_new_req = false;
             if (!req) {
                 /*
                  * Dispatch queue is empty so set flags for
                  * mmc_request_fn() to wake us up.
                  */
                 if (mq->qcnt)
                     cntx->is_waiting_last_req = true;
                 else
                     mq->asleep = true;
             }
             spin_unlock_irq(q->queue_lock);
     
             if (req || mq->qcnt) {
                 set_current_state(TASK_RUNNING);
                 mmc_blk_issue_rq(mq, req);
                 cond_resched();
             } else {
                 if (kthread_should_stop()) {
                     set_current_state(TASK_RUNNING);
                     break;
                 }
                 up(&mq->thread_sem);
                 schedule();
                 down(&mq->thread_sem);
             }
         } while (1);
        up(&mq->thread_sem);
    
        return 0;
    }
    
    

    参考文档:

    kernel doc - Documentation/block
    Linux块设备IO子系统(一) _驱动模型 - https://www.cnblogs.com/xiaojiang1025/p/6500557.html

    展开全文
  • linux系统调用实现机制详解(内核4.14.4)https://yq.aliyun.com/articles/522766?spm=a2c4e.11155435.0.0.25d33312xbNbM51.1 linux系统调用介绍linux内核中设置了一组用于实现系统功能的子程序,称为系统调用。...

    linux系统调用实现机制详解(内核4.14.4)

    https://yq.aliyun.com/articles/522766?spm=a2c4e.11155435.0.0.25d33312xbNbM5


    1.1     linux系统调用介绍

    linux内核中设置了一组用于实现系统功能的子程序,称为系统调用。和普通库函数调用相似,只是系统调用由操作系统核心提供,运行于核心态,而普通的函数调用由函数库或用户自己提供,运行于用户态。

    在Linux中,每个系统调用被赋予一个系统调用号。通过这个独一无二的号就可以关联系统调用。当用户空间的进程执行一个系统调用的时候,这个系统调用号就被用来指明到底是要执行哪个系统调用。

    系统调用号一旦分配就不能再有任何变更,否则编译好的应用程序就会崩溃。Linux有一个“未实现”系统调用sys_ni_syscall(),它除了返回一ENOSYS外不做任何其他工作,这个错误号就是专门针对无效的系统调用而设的。

    结合具体源码来看下实现机制。

    1.2     系统调用表和调用号

    具体号子分配在文件arch/x86/entry/syscalls/syscall_64.tbl中定义,如下:

    0       common  read                    sys_read

    1       common  write                   sys_write

    2       common  open                    sys_open

    3       common  close                   sys_close

    ………

    30      common  shmat                   sys_shmat

    31      common  shmctl                  sys_shmctl

    32      common  dup                     sys_dup

    33      common  dup2                    sys_dup2

    34      common  pause                   sys_pause

    35      common  nanosleep               sys_nanosleep

    36      common  getitimer               sys_getitimer

    37      common  alarm                   sys_alarm

    38      common  setitimer               sys_setitimer

    39      common  getpid                  sys_getpid

    40      common  sendfile                sys_sendfile64

    41      common  socket                  sys_socket

    …….

    也可以在arch/x86/include/generated/uapi/asm/unistd_64.h文件中查找到系统调用号。

    #define __NR_read 0

    #define __NR_write 1

    #define __NR_open 2

    #define __NR_close 3

    #define __NR_stat 4

    #define __NR_fstat 5

    #define __NR_lstat 6

    #define __NR_poll 7

    #define __NR_lseek 8

    ……

     

    1.2     系统调用声明

    在文件(include/linux/syscalls.h)中定义了系统调用函数声明,函数声明中的asmlinkage限定词,这用于通知编译器仅从栈中提取该函数的参数。所有的系统调用都需要这个限定词。例如系统调用getpid()在内核中被定义成sys_ getpid。这是Linux中所有系统调用都应该遵守的命名规则.

    如下:

                asmlinkage long sys_kill(pid_t pid, int sig);

    1.3     系统调用实现

    不同的系统调用实现在不同的文件中,例如sys_read 系统调用实现在fs/read_write.c文件中,sys_socket定义在net/socket.c中。

    例如sys_socket的原型如下:

    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol) 

                其中3表示有3个参数,用于解析参数时候使用。

    查看宏SYSCALL_DEFINE3的定义,定义也在include/linux/syscalls.h中,如下:

    #define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)

    #define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)

    #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

    #define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)

    #define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)

    #define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)

     

    #define SYSCALL_DEFINE_MAXARGS  6

     

    #define SYSCALL_DEFINEx(x, sname, ...)                          \

            SYSCALL_METADATA(sname, x, __VA_ARGS__)                 \

            __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

     

    #define __PROTECT(...) asmlinkage_protect(__VA_ARGS__)

    #define __SYSCALL_DEFINEx(x, name, ...)                                 \

            asmlinkage long sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))       \

                    __attribute__((alias(__stringify(SyS##name))));         \

            static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__));  \

            asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__));      \

            asmlinkage long SyS##name(__MAP(x,__SC_LONG,__VA_ARGS__))       \

            {                                                               \

                    long ret = SYSC##name(__MAP(x,__SC_CAST,__VA_ARGS__));  \

                    __MAP(x,__SC_TEST,__VA_ARGS__);                         \

                    __PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__));       \

                    return ret;                                             \

            }                                                               \

            static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__))

                我们看到SYSCALL_DEFINE3指向SYSCALL_DEFINEx,而SYSCALL_DEFINEx指向__SYSCALL_DEFINEx,在__SYSCALL_DEFINEx宏中调用真正的原型,如sys_socket(其也定义在syscalls.h)。

                所以SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)  就是sys_socket函数。具体实现后续会在linux协议栈中进行介绍。

                置于为什么会这么复杂,因为linux发展过程中难免碰到各种漏洞,有些则是因为修改漏洞需要,例如CVE-2009-0029漏洞

    https://bugzilla.redhat.com/show_bug.cgi?id=479969

     

    1.4     系统调用总接口

    之前在arch/x86/kernel/entry_64.S中实现了system_call的系统调用总接口。根据系统参数参数号来执行具体的系统调用。

    现在所有socket相关的系统调用,都会使用sys_socketcall的系统调用,如下socketcall的代码片段,根据参数进入switch…case…判断操作码,跳转至对应的系统接口:

    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)

    {

    ……

            switch (call) {

            case SYS_SOCKET:

                    err = sys_socket(a0, a1, a[2]);

                    break;

            case SYS_BIND:

                    err = sys_bind(a0, (struct sockaddr __user *)a1, a[2]);

                    break;

            case SYS_CONNECT:

                    err = sys_connect(a0, (struct sockaddr __user *)a1, a[2]);

                    break;

            case SYS_LISTEN:

                    err = sys_listen(a0, a1);

                    break;

            case SYS_ACCEPT:

                    err = sys_accept4(a0, (struct sockaddr __user *)a1,

                                      (int __user *)a[2], 0);

                    break;

            case SYS_GETSOCKNAME:

                    err =

                        sys_getsockname(a0, (struct sockaddr __user *)a1,

                                        (int __user *)a[2]);

                    break;

            case SYS_GETPEERNAME:

                    err =

                        sys_getpeername(a0, (struct sockaddr __user *)a1,

                                        (int __user *)a[2]);

                这里的变量定义在文件include/uapi/linux/net.h中,如下

    #define SYS_SOCKET      1               /* sys_socket(2)                */

    #define SYS_BIND        2               /* sys_bind(2)                  */

    #define SYS_CONNECT     3               /* sys_connect(2)               */

    #define SYS_LISTEN      4               /* sys_listen(2)                */

    #define SYS_ACCEPT      5               /* sys_accept(2)                */

    #define SYS_GETSOCKNAME 6               /* sys_getsockname(2)           */

    #define SYS_GETPEERNAME 7               /* sys_getpeername(2)           */

    #define SYS_SOCKETPAIR  8               /* sys_socketpair(2)            */

    #define SYS_SEND        9               /* sys_send(2)                  */

    #define SYS_RECV        10              /* sys_recv(2)                  */

    #define SYS_SENDTO      11              /* sys_sendto(2)                */

    #define SYS_RECVFROM    12              /* sys_recvfrom(2)              */

    #define SYS_SHUTDOWN    13              /* sys_shutdown(2)              */

    #define SYS_SETSOCKOPT  14              /* sys_setsockopt(2)            */

    #define SYS_GETSOCKOPT  15              /* sys_getsockopt(2)            */

    #define SYS_SENDMSG     16              /* sys_sendmsg(2)               */

    #define SYS_RECVMSG     17              /* sys_recvmsg(2)               */

    #define SYS_ACCEPT4     18              /* sys_accept4(2)               */

    #define SYS_RECVMMSG    19              /* sys_recvmmsg(2)              */

    #define SYS_SENDMMSG    20              /* sys_sendmmsg(2)              */

     

    1.5     系统调用流程

    整体的系统调用的过程如下,由应用程序调用C库提供的API函数,该API实现函数会调用内核的统一入口函数,具体到系统调用。

    86340e27c4c2b0fab0bd0667eec165877b893583

                图中逻辑为常用的系统调用。Socket相关的系统调用入口函数为sys_socketcall

    如果出现错误,错误码定义在文件:

    include/uapi/asm-generic/errno-base.h中。

                具体看下节中的socket系统调用。

    1.6     socket具体实现流程例子

    Socket 的API函数 socket ()(该函数定义在/usr/include/sys/socket.h文件中

    extern int socket (int __domain, int __type, int __protocol) __THROW;

                glibc库对socket系统调用进行了封装。位于文件

    sysdeps/unix/sysv/linux/i386/socket.S

                其中定义了#  define __socket socket,调用__socket就是调用socket函数。

                该函数是对socket函数的封装,代码中主要逻辑是调用sys_socketcall系统调用,参数为socket的调用号,然后用socketcall函数来进行调用socket。

    整体逻辑看上方图。

    可以编译一个使用socket系统调用的应用程序,进行gdb调试,运行到socket时候进行反汇编显示如下,下面标红的一行是移动0x29到eax,而0x29就是41,就是socket系统调用的系统号:

    (gdb) disass socket

    Dump of assembler code for function socket:

    => 0x00007ffff78f85a0 <+0>: mov    $0x29,%eax

       0x00007ffff78f85a5 <+5>:  syscall

       0x00007ffff78f85a7 <+7>:  cmp    $0xfffffffffffff001,%rax

       0x00007ffff78f85ad <+13>: jae    0x7ffff78f85b0 <socket+16>

       0x00007ffff78f85af <+15>: retq  

       0x00007ffff78f85b0 <+16>: mov    0x2bb8c1(%rip),%rcx        # 0x7ffff7bb3e78

       0x00007ffff78f85b7 <+23>: neg    %eax

       0x00007ffff78f85b9 <+25>: mov    %eax,%fs:(%rcx)

       0x00007ffff78f85bc <+28>: or     $0xffffffffffffffff,%rax

       0x00007ffff78f85c0 <+32>: retq  

    End of assembler dump.

     

    1.7     系统调用跟踪

    编写一个代码如下:

    #include <unistd.h>

    #include <fcntl.h>

    int main(){

        int handle,bytes;

        void * ptr;

        handle=open("tmp/test.txt",O_RDONLY);

        close(handle);

        return 0;

    }

    编译:gcc -o hell hello.c

    使用strace命令进行跟踪:

    # strace -o log.txt ./hello

    打开log.txt可以看到如下内容:

    execve("./hello"["./hello"][/* 22 vars */]) = 0

    brk(NULL)                               = 0xa1e000

    access("/etc/ld.so.nohwcap"F_OK)      = -1 ENOENT (No such file or directory)

    mmap(NULL8192PROT_READ|PROT_WRITEMAP_PRIVATE|MAP_ANONYMOUS, -10) = 0x7fcc310ad000

    access("/etc/ld.so.preload"R_OK)      = -1 ENOENT (No such file or directory)

    open("/etc/ld.so.cache"O_RDONLY|O_CLOEXEC) = 3

    fstat(3{st_mode=S_IFREG|0644, st_size=71985, ...}) = 0

    mmap(NULL71985PROT_READMAP_PRIVATE30) = 0x7fcc3109b000

    close(3)                                = 0

    access("/etc/ld.so.nohwcap"F_OK)      = -1 ENOENT (No such file or directory)

    open("/lib/x86_64-linux-gnu/libc.so.6"O_RDONLY|O_CLOEXEC) = 3

    read(3"\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832

    fstat(3{st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0

    mmap(NULL3971488PROT_READ|PROT_EXECMAP_PRIVATE|MAP_DENYWRITE30) = 0x7fcc30ac0000

    mprotect(0x7fcc30c800002097152PROT_NONE) = 0

    mmap(0x7fcc30e8000024576PROT_READ|PROT_WRITEMAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE30x1c0000) = 0x7fcc30e80000

    mmap(0x7fcc30e8600014752PROT_READ|PROT_WRITEMAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -10) = 0x7fcc30e86000

    close(3)                                = 0

    mmap(NULL4096PROT_READ|PROT_WRITEMAP_PRIVATE|MAP_ANONYMOUS, -10) = 0x7fcc3109a000

    mmap(NULL4096PROT_READ|PROT_WRITEMAP_PRIVATE|MAP_ANONYMOUS, -10) = 0x7fcc31099000

    mmap(NULL4096PROT_READ|PROT_WRITEMAP_PRIVATE|MAP_ANONYMOUS, -10) = 0x7fcc31098000

    arch_prctl(ARCH_SET_FS0x7fcc31099700) = 0

    mprotect(0x7fcc30e8000016384PROT_READ) = 0

    mprotect(0x6000004096PROT_READ)     = 0

    mprotect(0x7fcc310af0004096PROT_READ) = 0

    munmap(0x7fcc3109b00071985)           = 0

    open("tmp/test.txt"O_RDONLY)          = -1 ENOENT (No such file or directory)

    close(-1)                               = -1 EBADF (Bad file descriptor)

    exit_group(0)                           = ?

    +++ exited with 0 +++

    注意到最后几行就是我们程序的系统中实现的系统调用。

    看到open返回的是-1(ENOENT),因为在tmp目录中不存在test.txt文件。而且我们程序中没有对文件打开与否进行判断,导致出错了应用也不知道,只能通过strace来进行跟踪。

    这个也是在(文件include/uapi/asm-generic/errno-base.h中定义的

    #define ENOENT           2      /* No such file or directory */)

    我们再来看一下之前的一大堆调用,这是为支持我们写的程序运行,系统进行的进程创建、内存映射等等工作。我们在代码中只写了几行,但是系统却在编译链接以及加载到内存的时候做了非常多的事情。

    所以,在开发应用程序的时候还会觉得麻烦么?最麻烦的事情底层其实都已经帮我们做好了,实在是找不到借口和老板说应用程序开发很麻烦了哦。

                创建一个tmp/test.txt文件,再调用发现最后三行如下:

    open("tmp/test.txt"O_RDONLY)          = 3

    close(3)                                = 0

    exit_group(0)                           = ?

                说明打开正确了。后续如果要诊断程序的系统调用问题可以使用strace函数。

     

    1.8     小结

    由于网络上关于系统的调用的介绍代码引用比较分散切老旧对新步入的同学造成不同的费解,因此总结此文。

    本文基于内核4.14.14代码介绍了linux系统调用,将系统调用表、调用号所在源码位置标出,同时梳理的系统调用的整个执行逻辑。最后剖析了socket用户接口和sys_socket系统调用之间的关系。针对函数细节没有进行深入,这个未来会有专项课题。


    展开全文
  • 2、编译linux内核 首先获取linux内核源码:git clone https://github.com/Xilinx/linux-xlnx.git 然后进到下载目录下的linux-xlnx,设置交叉编译环境变量,该变量可以设置在Makefile文件里面: 例如:CROSS_...

    1、编译uboot,可以参考http://www.wiki.xilinx.com/Build%20U-Boot

    2、编译linux内核
    首先获取linux内核源码:git clone https://github.com/Xilinx/linux-xlnx.git
    然后进到下载目录下的linux-xlnx,设置交叉编译环境变量,该变量可以设置在Makefile文件里面:
    例如:CROSS_COMPILE=CROSS_COMPILE=/home/rl/Codesourcery/bin/arm-xilinx-linux-gnueabi-
    接下来开始编译内核
    1、make ARCH=arm xilinx_zynq_defconfig

    2、make ARCH=arm menuconfig
    需要设置usb gadget support如图所示
    这里写图片描述
    3、编译内核 sudo make ARCH=arm uImage modules UIMAGE_LOADADDR=0x00008000
    这里写图片描述
    如图所示:arch/arm/boot/uImage是生成的内核文件,需要copy到SD卡上。
    以下驱动也需要copy到sd卡上
    drivers/usb/gadget/function/u_ether.ko
    drivers/usb/gadget/function/usb_f_rndis.ko
    drivers/usb/gadget/libcomposite.ko
    4、编译dtb文件
    首先需要修改devicetree,在arch/arm/boot/dts文件下,本文采用了zc702平台
    修改arch/arm/boot/dts/zynq-zc702.dts
    这里写图片描述
    dr_mode由host改为peripheral
    然后编译dtb文件,回到linux-xlnx文件,sudo make ARCH=arm dtbs
    然后在arch/arm/boot/dts文件下生成zynq-zc702.dtb,copyzynq-zc702.dtb到sd卡,命名为devicetree.dtb

    5、uramdisk.image.gz用的从xilinx wiki上下载的,若要编译,参考xilinx wiki
    sd卡上最后有如下文件
    这里写图片描述

    6、将sd卡从插入zc702卡槽,linux就起来了。
    mount /dev/mmcblk0p1 /mnt
    执行如下脚本
    这里写图片描述
    本文整理成usb_ehter_boot.sh,
    #!/bin/sh
    insmod /mnt/libcomposite.ko
    insmod /mnt/u_ether.ko
    insmod /mnt/usb_f_rndis.ko
    mount -t configfs none /sys/kernel/config
    cd /sys/kernel/config/usb_gadget
    mkdir g1
    cd g1
    echo "64" > bMaxPacketSize0
    echo "0x200" > bcdUSB
    echo "0x100" > bcdDevice
    echo "0x03FD" > idVendor
    echo "0x0500" > idProduct
    mkdir functions/rndis.rn0
    mkdir configs/c1.1
    ln -s functions/rndis.rn0 configs/c1.1/
    echo "ci_hdrc.0" > UDC
    ifconfig usb0 192.168.1.200
    ifconfig usb0 up
    ~

    本例将zc702 ip设置为192.168.1.200,在host pc上 设置主机ip为192.168.1.100如图所示
    这里写图片描述
    接下来可以互ping
    这里写图片描述
    zc702和host pc间usb 网络就通了。
    主机可以通ftp访问zc702.如ftp 192.168.1.200
    这里写图片描述

    接下来开发其他应用。

    展开全文
  • 正如所承诺的,Linux内核维护者Greg Kroah-Hartman今天发布了针对长期支持的Linux 4.14,4.9,4.4和3.18内核系列的一系列新更新。 这些新内核在他们之前发布的一个星期后,添加了更多的x86更新,以保护用户不...
    正如所承诺的,Linux内核维护者Greg Kroah-Hartman今天发布了针对长期支持的Linux 4.14,4.9,4.4和3.18内核系列的一系列新更新。

    这些新内核在他们之前发布的一个星期后,添加了更多的x86更新,以保护用户不受本月初的Meltdown和Spectre安全漏洞的影响。他们还包括各种更新的GPU,InfiniBand,USB,加密,声音,网络驱动程序,以及一些核心网络变化。

    Linux kernel 4.14.14总共改变了130个文件,其中2524个插入和542个删除,Linux kernel 4.9.77个LTS改变了112个文件,其中2306个插入和466个删除,Linux kernel 4.4.112个LTS改变了90个文件,1811个插入和523个删除,Linux kernel 3.18.92更改了38个文件,其中487个插入和192个删除。

    Linux内核4.14.14,4.9.77,4.4.112和3.18.92更新发布Linux内核4.14.14,4.9.77,4.4.112和3.18.92更新发布

    相关:内核开发者称应更新Linux内核应对 Meltdown 和 Spectre漏洞  http://www.linuxidc.com/Linux/2018-01/150204.htm

    所有用户必须尽快更新他们的系统

    正如Greg Kroah-Hartman在本月早些时候发布的关于Linux内核中的Meltdown和Spectre补丁状态的更新中指出的那样,用户必须习惯于随时保持系统的最新状态,特别是当一个新的内核版本可用。因此,如果您收到这些内核,请立即更新您的系统。

    我们强烈建议所有操作系统供应商为其GNU/Linux发行版下载和编译Linux 4.14.14,4.9.77,4.4.112或3.18.92内核,并将它们作为稳定软件存储库中的更新发布,供用户安装他们尽快。您可以从kernel.org获取这些新内核版本的源代码。

    更新的git树在

      • git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git linux-4.14.y
      • git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git linux-4.9.y
      • git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git linux-4.4.y
      • git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git linux-3.18.y
    展开全文
  • 9月份,Linux内核开发人员格雷格•克罗-哈特曼(Greg Kroah-Hartman)在其个人博客上证实,Linux内核4.14是下一个LTS内核,这个内核将至少被支持两年;最近这个年限被延长到了六年。因此,Linux 4.14的开发周期比...
  • 1. 修改DTS文件:  sun8i-h3-nanopi.dtsi 中, 增加 matrix = &matrix;   1 / { 2 aliases { 3 serial0 = &uart0; 4 serial1 = &uart1; 5 serial2 = &...
  • 2019独角兽企业重金招聘Python工程师标准>>> ...
  • Linux 块设备驱动 (4)

    2018-03-04 16:19:27
    1. 背景 让我们梳理一下本系列...然后,在 Linux Block Driver - 2 中,我们在 Sampleblk 驱动创建了 Ext4 文件系统,并做了一个 fio 顺序测试。测试中我们利用 Linux 的各种跟踪工具,对这个 fio 测试做了一个...
  • 1. 确保linux内核版本大于 4.14 如何查看linux 内核版本 :终端 uname -sr 如果内核版本低于 4.14:升级linux内核 ubuntu可以参照 https://www.linuxidc.com/Linux/2017-03/141940.htm 升级完记得重启 2. ...
  • 先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题 ...
  • 文章目录块设备块设备体系结构体系结构层次块设备驱动程序设计块设备描述设备注册设备操作I/O请求请求队列块设备完整驱动程序块设备驱动测试数据访问流程BIO__make_request 块设备 块设备将数据存储在固定的块中,每...
  • 本节不需要编写驱动,直接可以使用i2c-dev中编写号的驱动,来进行使用。 也就是说这是一个完全的应用程序。 程序在后面的代码仓库。这里说一下原理。 正常情况下,每一个adaptor,都会在i2c_dev_init函数调用时...
  • 内核源码:linux-4.14BSP(全志H3厂商全志科技提供) 步骤: 1.安装交叉编译器 访问此处下载地址的toolchain目录,下载交叉编译器arm-cortexa9-linux-gnueabihf-4.9.3.tar.xz,然后解压编译器: $ m...
  • Xilinx Petalinux2018.2 Kernel4.14支持设备树,drivers\input\touchscreen\goodix.c,Xilinx Petalinux2015.2.1 Kernel3.19只支持ACPI,这需要更新。 #ifdef CONFIG_ACPI static const struct acpi_device_id ...
  • 移植Linux4.14.1到JZ2440

    2020-04-03 23:08:42
    驱动目前只支持Nandflash和串口。 1. 准备材料 开发主机:Ubuntu18.04 64位 Linux内核:4.14.1(下载) u-boot,dnw:来自韦东山 交叉编译器:arm-linux-gnueabi(版本:5.4.1 20170404,下载) YAFFS2...
  • 本课程是linux驱动开发的第4个课程,主要内容是驱动框架的引入。通过led驱动框架和gpiolib的这两个框架的详细解释,让大家学习内核中驱动框架的使用方法,这个对后续学习基于框架的复杂驱动非常有帮助。
  • 首先希望实现硬件的驱动编写,网上这方面资料杂并且久,水平较低折腾了一天,终于了一个可以跑的驱动模块,整理在这里,备忘并供需要的朋友参考。 开发环境 PC:ubuntu 64位 ssh接入显示信息如下 Welcome to ...
  • linux下的第一个驱动程序 hello (for 2.4)我也照着上面的方法试着做了。环境:radhet9(文本模式下) gcc(3.2.2)试验1#define MODULE#includesint init_module(void){ printk("Hello,world/n"); return 0;}void ...
  •  通过分析块设备驱动的框架,知道如何来写驱动   1.之前我们学的都是字符设备驱动,先来回忆一下 字符设备驱动: 当我们的应用层读写(read()/write())字符设备驱动时,是按字节/字符来读写数据的,期间没有任何...
1 2 3 4 5 ... 20
收藏数 1,093
精华内容 437