https://baike.baidu.com/item/ioctl/6392403?fr=aladdin
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显然不会这么做;还有一个问题就是系统调用是需要提供参数,并且具有返回值的,这些参数又是怎么传递的?也就是说,对于系统调用我们要搞清楚两点:
- 系统调用的函数名称转换。
- 系统调用的参数传递。
首先看第一个问题。实际上,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, ®s);
}