ioctrl 内部原理 copy_from_user

https://baike.baidu.com/item/ioctl/6392403?fr=aladdin

 

https://blog.csdn.net/ce123_zhouwei/article/details/8454226?utm_medium=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-1.nonecase&depth_1-utm_source=distribute.pc_relevant_bbs_down.none-task-blog-baidujs-1.nonecase

linux内核中的copy_to_user和copy_from_user

 

https://blog.csdn.net/liuhangtiant/article/details/85227125

 

 

https://blog.csdn.net/styyzxjq2009/article/details/8023501/

 

简单介绍一下函数:

int (*ioctl) (struct inode * node, struct file *filp, unsigned int cmd, unsigned long arg);

参数:

1)inode和file:ioctl的操作有可能是要修改文件的属性,或者访问硬件。要修改

文件属性的话,就要用到这两个结构体了,所以这里传来了它们的指针。

2)cmd:命令,接下来要长篇大论地说。

3)arg:参数,接下来也要长篇大论。

返回值:

1)如果传入的非法命令,ioctl返回错误号-EINVAL。

2)内核中的驱动函数返回值都有一个默认的方法,只要是正数,内核就会傻乎乎的认为这是正确的返回,并把它传给应用层,如果是负值,内核就会认为它是错误号了。

Ioctl里面多个不同的命令,那就要看它函数的实现来决定返回值了。打个比方,如果ioctl里面有一个类似read的函数,那返回值也就可以像read一样返回。

当然,不返回也是可以的。

 

linux-2.3.11/linux/drivers/net/rrunner.c


static int rr_ioctl(struct device *dev, struct ifreq *rq, int cmd)
{
	struct rr_private *rrpriv;
	unsigned char *image, *oldimage;
	unsigned int i;
	int error = -EOPNOTSUPP;

	rrpriv = (struct rr_private *)dev->priv;

	spin_lock(&rrpriv->lock);

	switch(cmd){
	case SIOCRRGFW:
		if (!suser()){
			error = -EPERM;
			goto out;
		}

		if (rrpriv->fw_running){
			printk("%s: Firmware already running\n", dev->name);
			error = -EPERM;
			goto out;
		}

		image = kmalloc(EEPROM_WORDS * sizeof(u32), GFP_KERNEL);
		if (!image){
			printk(KERN_ERR "%s: Unable to allocate memory "
			       "for EEPROM image\n", dev->name);
			error = -ENOMEM;
			goto out;
		}
		i = rr_read_eeprom(rrpriv, 0, image, EEPROM_BYTES);
		if (i != EEPROM_BYTES){
			kfree(image);
			printk(KERN_ERR "%s: Error reading EEPROM\n",
			       dev->name);
			error = -EFAULT;
			goto out;
		}
		error = copy_to_user(rq->ifr_data, image, EEPROM_BYTES);
		if (error)
			error = -EFAULT;
		kfree(image);
		break;
	case SIOCRRPFW:
		if (!suser()){
			error = -EPERM;
			goto out;
		}

 

 

 

1.编译内核示例  https://blog.csdn.net/weixin_43713334/article/details/106277373

#include <linux/init.h>
#include <linux/slab.h>
#include <linux/sched.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/vmalloc.h>
#include <asm/uaccess.h>
#include <linux/types.h>

struct color{
    int red;
    int blue;
    int green;
    struct list_head list;
};

static LIST_HEAD(color_list);
/**
 * Function prototypes
 */

/* This function is called when the module is loaded. */
static int list_init(void)
{
    struct color *color[4], *ptr;
    int i = 0;
    
    for (; i < 4; ++i){
	    color[i] = kmalloc(sizeof(*ptr), GFP_KERNEL);
	    color[i]->red = i;
	    color[i]->blue = i;
	    color[i]->green = i;
	    INIT_LIST_HEAD(&color[i]->list);
	    list_add_tail(& (color[i]->list), &color_list);
    }
	
    printk(KERN_INFO "module loaded");

    list_for_each_entry(ptr, &color_list, list)
    {
        printk(KERN_INFO "red = [%d] blue = [%d] green = [%d]", ptr->red, ptr->blue, ptr->green);
    }

    
    return 0;  
}
/* This function is called when the module is removed. */
static void list_exit(void)
{
    struct color *ptr, *next;

    printk( KERN_INFO "module removed\n");
    
    list_for_each_entry_safe(ptr, next, &color_list, list)
    {
        list_del(&ptr->list);
        kfree(ptr);
    }
}

/* Macros for registering module entry and exit points. */
module_init( list_init );
module_exit( list_exit );

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Module");
MODULE_AUTHOR("SGG");

2.编译提示找不到linux / init.h:没有这样的文件或目录   参考http://www.cocoachina.com/articles/48935

   uname -a  //查看内核版本
   sudo apt-get install linux-headers-5.4.0-66-generic  //安装包

3.修改makefile如下

makefile

ifneq ($(KERNELRELEASE),)
    # call from kernel build system
    lifo-objs := main.o
    obj-m   := lifo.o
else
   KERNELDIR ?= /lib/modules/$(shell uname -r)/build
   PWD       := $(shell pwd)
modules:
    echo $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/../include modules
    $(MAKE) -C $(KERNELDIR) M=$(PWD) LDDINC=$(PWD)/../include modules
endif

clean:  
    rm -rf *.o *~ core .depend *.mod.o .*.cmd *.ko *.mod.c \n    .tmp_versions *.markers *.symvers modules.order

depend .depend dep:
    $(CC) $(CFLAGS) -M *.c > .depend

ifeq (.depend,$(wildcard .depend))
    include .depend
endif

执行结果

4.xxxx@xxx-OptiPlex-7060:~/Chapter9/9_3_4/11$ make
echo make -C /lib/modules/5.4.0-66-generic/build M=~/Chapter9/9_3_4/11/../include modules
make -C /lib/modules/5.4.0-66-generic/build M=~/Chapter9/9_3_4/11 LDDINC=~/Chapter9/9_3_4/11/../include modules
make -C /lib/modules/5.4.0-66-generic/build M=~/Chapter9/9_3_4/11 LDDINC=~/Chapter9/9_3_4/11/../include modules
make[1]: 进入目录“/usr/src/linux-headers-5.4.0-66-generic”
  CC [M]  ~/Chapter9/9_3_4/11/main.o
  LD [M]  ~/Chapter9/9_3_4/11/lifo.o
  Building modules, stage 2.
  MODPOST 1 modules
  CC [M]  ~/Chapter9/9_3_4/11/lifo.mod.o
  LD [M]  ~/Chapter9/9_3_4/11/lifo.ko
make[1]: 离开目录“/usr/src/linux-headers-5.4.0-66-generic”

5.

sudo insmod lifo.ko

dmsg
 

6.结果

修改源码在输出语句那里没有最后差一个换行符 \n.再卸载驱动模块 clean 再make 再insmod ,dmesg打印内核输出结果

sudo rmmod lifo

make clean

make

sudo insmod lifo.ko

dmesg

dmesg 打印内核输出结果

 

系统调用

https://www.cnblogs.com/alantu2018/p/8991310.html

linux-2.3.11/linux/include/asm-arm/unistd.h

#ifndef __ASM_ARM_UNISTD_H
#define __ASM_ARM_UNISTD_H

#define __NR_SYSCALL_BASE	0x900000

/*
 * This file contains the system call numbers.
 */

#define __NR_setup			(__NR_SYSCALL_BASE+  0)	/* used only by init, to get system going */
#define __NR_exit			(__NR_SYSCALL_BASE+  1)
#define __NR_fork			(__NR_SYSCALL_BASE+  2)
#define __NR_read			(__NR_SYSCALL_BASE+  3)
#define __NR_write			(__NR_SYSCALL_BASE+  4)
#define __NR_open			(__NR_SYSCALL_BASE+  5)
#define __NR_close			(__NR_SYSCALL_BASE+  6)
#define __NR_waitpid			(__NR_SYSCALL_BASE+  7)
#define __NR_creat			(__NR_SYSCALL_BASE+  8)
#define __NR_link			(__NR_SYSCALL_BASE+  9)
#define __NR_unlink			(__NR_SYSCALL_BASE+ 10)
#define __NR_execve			(__NR_SYSCALL_BASE+ 11)
#define __NR_chdir			(__NR_SYSCALL_BASE+ 12)
#define __NR_time			(__NR_SYSCALL_BASE+ 13)
#define __NR_mknod			(__NR_SYSCALL_BASE+ 14)
#define __NR_chmod			(__NR_SYSCALL_BASE+ 15)
#define __NR_lchown			(__NR_SYSCALL_BASE+ 16)
#define __NR_break			(__NR_SYSCALL_BASE+ 17)
#define __NR_oldstat			(__NR_SYSCALL_BASE+ 18)
#define __NR_lseek			(__NR_SYSCALL_BASE+ 19)
#define __NR_getpid			(__NR_SYSCALL_BASE+ 20)

 

 

 

 

 

 

 

https://blog.csdn.net/yu132563/article/details/79431844 系统调用执行的过程 大致:

每个系统调用都是通过一个单一的入口点多路传入内核。eax 寄存器用来标识应当调用的某个系统调用,这在 C 库中做了指定(来自用户空间应用程序的每个调用)。当加载了系统的 C 库调用索引和参数时,就会调用一个软件中断(0x80 中断),它将执行 system_call 函数(通过中断处理程序),这个函数会按照 eax 内容中的标识处理所有的系统调用。在经过几个简单测试之后,使用 system_call_table 和 eax 中包含的索引来执行真正的系统调用了。从系统调用中返回后,最终执行 syscall_exit,并调用 resume_userspace 返回用户空间。然后继续在 C 库中执行,它将返回到用户应用程序中。

https://www.cnblogs.com/alantu2018/p/8991310.html

1.

为什么需要系统调用


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

一般的,进程是不能访问内核的。它不能访问内核所占内存空间也不能调用内核函数。CPU硬件决定了这些(这就是为什么它被称作“保护模式”(详细参见深入理解计算机系统-之-内存寻址(二)–存储保护机制(CPU实模式与保护模式)))。

为了和用户空间上运行的进程进行交互,内核提供了一组接口。透过该接口,应用程序可以访问硬件设备和其他操作系统资源。这组接口在应用程序和内核之间扮演了使者的角色,应用程序发送各种请求,而内核负责满足这些请求(或者让应用程序暂时搁置)。实际上提供这组接口主要是为了保证系统稳定可靠,避免应用程序肆意妄行,惹出大麻烦。

系统调用在用户空间进程和硬件设备之间添加了一个中间层。该层主要作用有三个:

  • 它为用户空间提供了一种统一的硬件的抽象接口。比如当需要读些文件的时候,应用程序就可以不去管磁盘类型和介质,甚至不用去管文件所在的文件系统到底是哪种类型。

  • 系统调用保证了系统的稳定和安全。作为硬件设备和应用程序之间的中间人,内核可以基于权限和其他一些规则对需要进行的访问进行裁决。举例来说,这样可以避免应用程序不正确地使用硬件设备,窃取其他进程的资源,或做出其他什么危害系统的事情。

  • 每个进程都运行在虚拟系统中,而在用户空间和系统的其余部分提供这样一层公共接口,也是出于这种考虑。如果应用程序可以随意访问硬件而内核又对此一无所知的话,几乎就没法实现多任务和虚拟内存,当然也不可能实现良好的稳定性和安全性。在Linux中,系统调用是用户空间访问内核的惟一手段;除异常和中断外,它们是内核惟一的合法入口

2.

系统调用的实现原理


基本机制


前文已经提到了Linux下的系统调用是通过0x80实现的,但是我们知道操作系统会有多个系统调用(Linux下有319个系统调用),而对于同一个中断号是如何处理多个不同的系统调用的?最简单的方式是对于不同的系统调用采用不同的中断号,但是中断号明显是一种稀缺资源,Linux显然不会这么做;还有一个问题就是系统调用是需要提供参数,并且具有返回值的,这些参数又是怎么传递的?也就是说,对于系统调用我们要搞清楚两点:

  1. 系统调用的函数名称转换。
  2. 系统调用的参数传递。

首先看第一个问题。实际上,Linux中每个系统调用都有相应的系统调用号作为唯一的标识,内核维护一张系统调用表,sys_call_table,表中的元素是系统调用函数的起始地址,而系统调用号就是系统调用在调用表的偏移量。在x86上,系统调用号是通过eax寄存器传递给内核的。比如fork()的实现:

用户空间的程序无法直接执行内核代码。它们不能直接调用内核空间中的函数,因为内核驻留在受保护的地址空间上。如果进程可以直接在内核的地址空间上读写的话,系统安全就会失去控制。所以,应用程序应该以某种方式通知系统,告诉内核自己需要执行一个系统调用,希望系统切换到内核态,这样内核就可以代表应用程序来执行该系统调用了。

通知内核的机制是靠软件中断实现的。首先,用户程序为系统调用设置参数。其中一个参数是系统调用编号。参数设置完成后,程序执行“系统调用”指令。x86系统上的软中断由int产生。这个指令会导致一个异常:产生一个事件,这个事件会致使处理器切换到内核态并跳转到一个新的地址,并开始执行那里的异常处理程序。此时的异常处理程序实际上就是系统调用处理程序。它与硬件体系结构紧密相关。

新地址的指令会保存程序的状态,计算出应该调用哪个系统调用,调用内核中实现那个系统调用的函数,恢复用户程序状态,然后将控制权返还给用户程序。系统调用是设备驱动程序中定义的函数最终被调用的一种方式。

从系统分析的角度,linux的系统调用涉及4个方面的问题

 

3.

响应函数sys_xxx


响应函数名以“sys_”开头,后跟该系统调用的名字。

例如

系统调用fork()的响应函数是sys_fork()(见Kernel/fork.c),

exit()的响应函数是sys_exit()(见kernel/fork.)。

系统调用表与系统调用号-=>数组与下标


文件include/asm/unisted.h为每个系统调用规定了唯一的编号。

linux-2.3.11/linux/arch/i386/kernel/entry.S

.data
ENTRY(sys_call_table)
	.long SYMBOL_NAME(sys_ni_syscall)	/* 0  -  old "setup()" system call*/
	.long SYMBOL_NAME(sys_exit)
	.long SYMBOL_NAME(sys_fork)

系统调用表sys_call_table记录了各sys_name函数在表中的位置,共190项。有了这张表,就很容易根据特定系统调用在表中的偏移量,找到对应的系统调用响应函数的入口地址。系统调用表共256项,余下的项是可供用户自己添加的系统调用空间。

在Linux中,每个系统调用被赋予一个系统调用号。这样,通过这个独一无二的号就可以关联系统调用。

linux-2.3.11/linux/arch/i386/kernel/process.c

asmlinkage int sys_fork(struct pt_regs regs)
{
	return do_fork(SIGCHLD, regs.esp, &regs);
}

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值