字符设备驱动基础(一)
目录
一、Linux内核对设备的分类
linux的文件种类:
Linux内核按驱动程序实现模型框架的不同,将设备分为三类:
总体框架图:
二、设备号------内核中同类设备的区分
三、申请和注销设备号
四、函数指针复习
4.1、 内存四区
4.2、C语言中内存数据的访问方式
4.3、C语言中函数调用方式:
4.4 适用场合
五、注册字符设备
六、字符设备驱动框架解析
6.1 两个操作函数中常用的结构体说明
6.2 字符设备驱动程序框架分析
6.3 参考原理图
6.4 常用操作函数说明
七、读操作实现
八、写操作实现
九、ioctl操作实现
十、printk
十一、多个次设备的支持
一、Linux内核对设备的分类
(unix家族有个思想一切皆文件)
linux的文件种类:
1. -:普通文件
2. d:目录文件
3. p:管道文件
4. s:本地socket文件
5. l:链接文件
6. c:字符设备
7. b:块设备
(每个文件都有文件名、元信息、文件内容。这7种文件都有这三个部分。只是他们的文件内容不同。元文件内有用户信息用户组信息创建时间什么的。他用一个inode结构体对象来存。
再外存中没有管道文件的文件内容。本地socket文件也相当于没有文件内容,他只是本地客户端和服务器的地址值;类似于网络socket的ip+端口号。软连接的内容是它指向文件的路径,硬链接是别名。字符设备和块设备只是提供了一个文件名,告诉我们操作哪个文件相当于操作这些设备。)
Linux内核按驱动程序实现模型框架的不同,将设备分为三类:
1. 字符设备:按字节流形式进行数据读写的设备,一般情况下按顺序访问,数据量不大,一般不设缓存(不部分情况顺序访问,当然也可随机访问)
2. 块设备:按整块进行数据读写的设备,最小的块大小为512字节(一个扇区),块的大小必须是扇区的整数倍,Linux系统的块大小一般为4096字节(4K),随机访问,设缓存以提高效率
(块设备一般都会套上一个文件系统,机械硬盘就是一圈一圈的磁道。磁道间被分成多个扇区)
(通过磁头来读取,这只是一个盘。如果还有一个盘顺序读取要读完这个盘再读下一个太慢了。他会直接把磁头放到另一个盘上。这个东西非常的慢所以文件系统通过缓存机制来加快一些速度。)
3. 网络设备:针对网络数据收发的设备
(网卡、蓝牙都属于网络设备)
(所以一切皆文件是要打引号的因为网络设备就不是文件而是网卡)
总体框架图:
左面这俩就是open、close那套接口
右面就是socket那套接口
二、设备号------内核中同类设备的区分
内核用设备号来区分同类里不同的设备,设备号是一个无符号32位整数,数据类型为dev_t,设备号分为两部分:
1. 主设备号:占高12位,用来表示驱动程序相同的一类设备
2. 次设备号:占低20位,用来表示被操作的哪个具体设备
应用程序打开一个设备文件时,通过设备号来查找定位内核中管理的设备。
MKDEV宏用来将主设备号和次设备号组合成32位完整的设备号,用法:
```dev_t devno;int major = 251;//主设备号int minor = 2;//次设备号devno = MKDEV(major,minor);```
MAJOR宏用来从32位设备号中分离出主设备号,用法:
```dev_t devno = MKDEV(249,1);int major = MAJOR(devno);```
MINOR宏用来从32位设备号中分离出次设备号,用法:
```dev_t devno = MKDEV(249,1);int minor = MINOR(devno);```
如果已知一个设备的主次设备号,应用层指定好设备文件名,那么可以用mknod命令在/dev目录创建代表这个设备的文件,即此后应用程序对此文件的操作就是对其代表的设备操作,mknod用法如下:
```@ cd /dev@ mknod 设备文件名 设备种类(c为字符设备,b为块设备) 主设备号 次设备号 //ubuntu下需加sudo执行```
sudo mknod /dev/mytest c 289 0
这样就创建了一个字符设备。主设备号 289 次设备号 0
在应用程序中如果要创建设备可以调用系统调用函数mknod,其原型如下:
```int mknod(const char *pathname,mode_t mode,dev_t dev);pathname:带路径的设备文件名,无路径默认为当前目录,一般都创建在/dev下mode:文件权限 位或 S_IFCHR/S_IFBLKdev:32位设备号返回值:成功为0,失败-1```
三、申请和注销设备号
字符驱动开发的第一步是通过模块的入口函数向内核添加本设备驱动的代码框架,主要完成:
(可以不同的设备号针对同一个设备,但是不能多个设备用一个设备号)
1. 申请设备号
2. 定义、初始化、向内核添加代表本设备的结构体元素
```
int register_chrdev_region(dev_t from, unsigned count, const char *name)
功能:手动分配设备号,先验证设备号是否被占用,如果没有则申请占用该设备号
参数:
from:自己指定的设备号
count:申请的设备数量
name:/proc/devices文件中与该设备对应的名字,方便用户层查询主设备号
返回值:
成功为0,失败负数,绝对值为错误码
```
```
int alloc_chrdev_region(dev_t *dev,unsigned baseminor,unsigned count, const char *name)
功能:动态分配设备号,查询内核里未被占用的设备号,如果找到则占用该设备号
参数:
dev:分配设备号成功后用来存放分配到的设备号
baseminior:起始的次设备号,一般为0
count:申请的设备数量
name:/proc/devices文件中与该设备对应的名字,方便用户层查询主次设备号
返回值:
成功为0,失败负数,绝对值为错误码
```
分配成功后在/proc/devices 可以查看到申请到主设备号和对应的设备名,mknod时参数可以参考查到的此设备信息
```
void unregister_chrdev_region(dev_t from, unsigned count)
功能:释放设备号
参数:
from:已成功分配的设备号将被释放
count:申请成功的设备数量
```
释放后/proc/devices文件对应的记录消失
成功了
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>int major = 11;
int minor = 0;
int mychar_num = 1;int __init mychar_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);ret = register_chrdev_region(devno, mychar_num, "mychar");if(ret){ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");if(ret){printk("get devno failed\n");return -1;}major = MAJOR(devno);//Easy to miss *****}//printk("mychar is running\n");return 0;
}
void __exit mychar_exit(void)
{dev_t devno = MKDEV(major,minor);//printk("mychar will exit\n");unregister_chrdev_region(devno, mychar_num);
}
MODULE_LICENSE("GPL");module_init(mychar_init);
module_exit(mychar_exit);
四、函数指针复习
内存的作用-----用来存放程序运行过程中的
1. 数据
2. 指令
4.1、 内存四区
堆区
栈区
数据区
代码区
4.2、C语言中内存数据的访问方式
直接访问:通过所在空间名称去访问
间接访问:通过所在空间首地址去访问 \*地址值 此时的\*为间接访问运算符
4.3、C语言中函数调用方式:
直接调用:通过函数名去调用函数
间接调用:通过函数在代码区所对应的那份空间的首地址去调用
```c
int func(int a,int b)
{
//......
}
int (int a,int b) * pf;//语法错误
int *pf(int a,int b);//函数声明语句
int (*pf)(int a,int b);//定义一个函数指针(a和b可以不写)
pf = &func;//&运算符后面如果是函数名的话可以省略不写
pf = func;
y = func(3,4);//直接调用
y = (*pf)(3,4);//间接调用,*运算符后面如果是函数指针类型则可以省略不写
y = pf(3,4);//间接调用
typedef int myint;
typedef int (*)(int,int) pft;//语法错误
typedef int (*pft)(int,int) ;
pft pt;
```
4.4 适用场合
前提:当有很多个同类函数待被调用时
A处:知道所有函数名,由此处来决定B处将会调用哪个函数
B处:负责调用A处指定的函数
思考:A处如何告诉B处被调用的是哪个函数呢,无非两个办法:
1. 告诉B处函数名,怎么做呢?传字符串----“函数名”? C语言没有对应语法支持
2. 告诉B处对应函数在代码区的地址
五、注册字符设备
处理批量同类型对象先设计数据类型,再设计数据结构。为了处理字符设备就出现了下面这个结构体。
```
struct cdev
{
struct kobject kobj;//表示该类型实体是一种内核对象
struct module *owner;//填THIS_MODULE,表示该字符设备从属于哪个内核模块
const struct file_operations *ops;//指向空间存放着针对该设备的各种操作函数地址
struct list_head list;//链表指针域
dev_t dev;//设备号
unsigned int count;//设备数量
};
```
(内核虽然是用C写的。但是他使用的是面向对象的思想,这个cdev就相当于是kobject的子类)
自己定义的结构体中必须有一个成员为 struct cdev cdev,两种方法定义一个设备:
1. 直接定义:定义结构体全局变量
2. 动态申请:
`struct cdev * cdev_alloc()`
void cdev_init(struct cdev *cdev,const struct file_operations *fops)
```cstruct file_operations{struct module *owner; //填THIS_MODULE,表示该结构体对象从属于哪个内核模块int (*open) (struct inode *, struct file *); //打开设备int (*release) (struct inode *, struct file *); //关闭设备ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备loff_t (*llseek) (struct file *, loff_t, int); //定位long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备unsigned int (*poll) (struct file *, struct poll_table_struct *); //POLL机制,实现多路复用的支持int (*mmap) (struct file *, struct vm_area_struct *); //映射内核空间到用户层int (*fasync) (int, struct file *, int); //信号驱动//......};```
该对象各个函数指针成员都对应相应的系统调用函数,应用层通过调用系统函数来间接调用这些函数指针成员指向的设备驱动函数:
一般定义一个struct file_operations类型的全局变量并用自己实现各种操作函数名对其进行初始化
```int cdev_add(struct cdev *p,dev_t dev,unsigned int count)功能:将指定字符设备添加到内核参数:p:指向被添加的设备dev:设备号count:设备数量,一般填1```
```
void cdev_del(struct cdev *p)
功能:从内核中移除一个字符设备
参数:
p:指向被移除的字符设备
```
小结:
字符设备驱动开发步骤:
1. 如果设备有自己的一些控制数据,则定义一个包含struct cdev cdev成员的结构体struct mydev,其它成员根据设备需求,设备简单则直接用struct cdev
2. 定义一个struct mydev或struct cdev的全局变量来表示本设备;也可以定义一个struct mydev或struct cdev的全局指针(记得在init时动态分配)
3. 定义三个全局变量分别来表示主设备号、次设备号、设备数
4. 定义一个struct file_operations结构体变量,其owner成员置成THIS_MODULE
5. module init函数流程:a. 申请设备号 b. 如果是全局设备指针则动态分配代表本设备的结构体元素 c. 初始化struct cdev成员 d. 设置struct cdev的owner成员为THIS_MODULE e. 添加字符设备到内核
6. module exit函数:a. 注销设备号 b. 从内核中移除struct cdev c. 如果如果是全局设备指针则释放其指向空间
7. 编写各个操作函数并将函数名初始化给struct file_operations结构体变量
验证操作步骤:
1. 编写驱动代码mychar.c
2. make生成ko文件
3. insmod内核模块
4. 查阅字符设备用到的设备号(主设备号):cat /proc/devices | grep 申请设备号时用的名字
5. 创建设备文件(设备节点) : mknod /dev/??? c 上一步查询到的主设备号 代码中指定初始次设备号
6. 编写app验证驱动(testmychar_app.c)
7. 编译运行app,dmesg命令查看内核打印信息
linux内核使用的语法是C99
结构体里可以直接把成员这样赋值。这在标准C中是不允许的。上面结构体file和operations中间有下划线。
make
sudo dmesg -C
sudo insmod mychar.ko
dmesg
cat /proc/devices | grep mychar
sudo mknod /dev/mydev c 11 0
我英语太垃圾了,所以以后写注释用英语写了。为了锻炼一下我的英语。可以看懂手册和git上的大佬的注释。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>int major = 11;
int minor = 0;
int mychar_num = 1;struct cdev mydev;int mychar_open(struct inode *pnode, struct file *pfile)
{printk("mychar_open\n");return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{printk("mychar_close\n");return 0;
}struct file_operations myops = {.owner = THIS_MODULE,.open = mychar_open,.release = mychar_close,
};int __init mychar_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);/*Apply for device number*/ret = register_chrdev_region(devno, mychar_num, "mychar");if(ret){ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");if(ret){printk("get devno failed\n");return -1;}major = MAJOR(devno);//Easy to miss *****}/*Assign the 'struct cdev' a set of operation functions*/cdev_init(&mydev, &myops);/*Add 'struct cdev' to the kernel's data structure*/mydev.owner = THIS_MODULE;cdev_add(&mydev, devno, mychar_num);//add to Hash.return 0;
}
void __exit mychar_exit(void)
{dev_t devno = MKDEV(major,minor);//printk("mychar will exit\n");unregister_chrdev_region(devno, mychar_num);
}
MODULE_LICENSE("GPL");module_init(mychar_init);
module_exit(mychar_exit);
写个测试用的APP
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main(int argc, char *argv[])
{int fd = -1;if(argc < 2){printf("The argument is too few\n");return 1;}fd = open(argv[1], O_RDONLY);if(fd < 0){printf("open %s failed\n", argv[1]);return 2;}close(fd);fd = -1;return 0;
}
六、字符设备驱动框架解析
设备的操作函数如果比喻是桩的话(性质类似于设备操作函数的函数,在一些场合被称为桩函数),则:
驱动实现设备操作函数 ----------- 做桩
insmod调用的init函数主要作用 --------- 钉桩
rmmod调用的exitt函数主要作用 --------- 拔桩
应用层通过系统调用函数间接调用这些设备操作函数 ------- 用桩
6.1 两个操作函数中常用的结构体说明
```c内核中记录文件元信息的结构体struct inode{//....dev_t i_rdev;//设备号struct cdev *i_cdev;//如果是字符设备才有此成员,指向对应设备驱动程序中的加入系统的struct cdev对象//....}/*1. 内核中每个该结构体对象对应着一个实际文件,一对一2. open一个文件时如果内核中该文件对应的inode对象已存在则不再创建,不存在才创建3. 内核中用此类型对象关联到对此文件的操作函数集(对设备而言就是关联到具体驱动代码)*/```
每次用open都会调用struct file对象
```c读写文件内容过程中用到的一些控制性数据组合而成的对象------文件操作引擎(文件操控器)struct file{//...mode_t f_mode;//不同用户的操作权限,驱动一般不用loff_t f_pos;//position 数据位置指示器,需要控制数据开始读写位置的设备有用unsigned int f_flags;//open时的第二个参数flags存放在此,驱动中常用struct file_operations *f_op;//open时从struct inode中i_cdev的对应成员获得地址,驱动开发中用来协助理解工作原理,内核中使用void *private_data;//本次打开文件的私有数据,驱动中常来在几个操作函数间传递共用数据struct dentry *f_dentry;//驱动中一般不用,除非需要访问对应文件的inode,用法flip->f_dentry->d_inodeint refcnt;//引用计数,保存着该对象地址的位置个数,close时发现refcnt为0才会销毁该struct file对象//...};/*1. open函数被调用成功一次,则创建一个该对象,因此可以认为一个该类型的对象对应一次指定文件的操作2. open同一个文件多次,每次open都会创建一个该类型的对象3. 文件描述符数组中存放的地址指向该类型的对象4. 每个文件描述符都对应一个struct file对象的地址*/```
文件描述符就是数组的下标。
6.2 字符设备驱动程序框架分析
驱动实现端:
驱动使用端:
syscall_open函数实现的伪代码:
```cint syscall_open(const char *filename,int flag){dev_t devno;struct inode *pnode = NULL;struct cdev *pcdev = NULL;struct file *pfile = NULL;int fd = -1;/*根据filename在内核中查找该文件对应的struct inode对象地址找到则pnode指向该对象未找到则创建新的struct inode对象,pnode指向该对象,并从文件系统中读取文件的元信息到该对象*/if(/*未找到对应的struct inode对象*/){/*根据文件种类决定如何进行下面的操作,如果是字符设备则执行如下操作*//*从pnode指向对象中得到设备号*/devno = pnode->i_rdev;/*用devno在字符设备链表查找对应节点,并将该节点的地址赋值给pcdev*//*pcdev赋值给pnode的i_cdev成员*/pnode->i_cdev = pcdev;}/*创建struct file对象,并将该对象的地址赋值给pfile*/pfile->f_op = pnode->i_cdev->ops;pfile->f_flags = flag;/*调用驱动程序的open函数*/pfile->f_op->open(pnode,pfile,flag);/*将struct file对象地址填入进程的描述符数组,得到对应位置的下标赋值给fd*/return fd;}```
syscall_read函数实现的伪代码
```cint syscall_read(int fd,void *pbuf,int size){struct file *pfile = NULL;struct file_operations *fops = NULL;int cnt;/*将fd作为下标,在进程的描述符数组中获得struct file对象的地址赋值给pfile*//*从struct file对象的f_op成员中得到操作函数集对象地址赋值给fops*//*从操作函数集对象的read成员得到该设备对应的驱动程序中read函数,并调用之*/cnt = fops->read(pfile,pbuf,size,&pfile->f_pos);。。。。return cnt;}```
6.3 参考原理图
6.4 常用操作函数说明
```cint (*open) (struct inode *, struct file *); //打开设备/*指向函数一般用来对设备进行硬件上的初始化,对于一些简单的设备该函数只需要return 0,对应open系统调用,是open系统调用函数实现过程中调用的函数,*/int (*release) (struct inode *, struct file *); //关闭设备/*,指向函数一般用来对设备进行硬件上的关闭操作,对于一些简单的设备该函数只需要return 0,对应close系统调用,是close系统调用函数实现过程中调用的函数*/ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //读设备/*指向函数用来将设备产生的数据读到用户空间,对应read系统调用,是read系统调用函数实现过程中调用的函数*/ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //写设备/*指向函数用来将用户空间的数据写进设备,对应write系统调用,是write系统调用函数实现过程中调用的函数*/loff_t (*llseek) (struct file *, loff_t, int); //数据操作位置的定位/*指向函数用来获取或设置设备数据的开始操作位置(位置指示器),对应lseek系统调用,是lseek系统调用函数实现过程中调用的函数*/long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);//读写设备参数,读设备状态、控制设备/*指向函数用来获取、设置设备一些属性或设备的工作方式等非数据读写操作,对应ioctl系统调用,是ioctl系统调用函数实现过程中调用的函数*/unsigned int (*poll) (struct file *, struct poll_table_struct *);//POLL机制,实现对设备的多路复用方式的访问/*指向函数用来协助多路复用机制完成对本设备可读、可写数据的监控,对应select、poll、epoll_wait系统调用,是select、poll、epoll_wait系统调用函数实现过程中调用的函数*/int (*fasync) (int, struct file *, int); //信号驱动/*指向函数用来创建信号驱动机制的引擎,对应fcntl系统调用的FASYNC标记设置,是fcntl系统调用函数FASYNC标记设置过程中调用的函数*/```
七、读操作实现
```cssize_t xxx_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos);完成功能:读取设备产生的数据参数:filp:指向open产生的struct file类型的对象,表示本次read对应的那次openpbuf:指向用户空间一块内存,用来保存读到的数据count:用户期望读取的字节数ppos:对于需要位置指示器控制的设备操作有用,用来指示读取的起始位置,读完后也需要变更位置指示器的指示位置返回值:本次成功读取的字节数,失败返回-1```
put_user(x,ptr)
x:char、int类型的简单变量名
unsigned long copy_to_user (void __user * to, const void * from, unsigned long n)
成功为返回0,失败非0
八、写操作实现
```cssize_t xxx_write (struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos); 完成功能:向设备写入数据参数:filp:指向open产生的struct file类型的对象,表示本次write对应的那次openpbuf:指向用户空间一块内存,用来保存被写的数据count:用户期望写入的字节数ppos:对于需要位置指示器控制的设备操作有用,用来指示写入的起始位置,写完后也需要变更位置指示器的指示位置返回值:本次成功写入的字节数,失败返回-1```
__user表示用户空间
get_user(x,ptr)
x:char、int类型的简单变量名
unsigned long copy_from_user (void * to, const void __user * from, unsigned long n)
成功为返回0,失败非0
这个设备文件要重新创建一下。不重新创建会打不开。老师没创建也能打开有点神奇。然后一定要给写权限。
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>#define BUF_LEN 100int major = 11;
int minor = 0;
int mychar_num = 1;struct cdev mydev;char mydev_buf[BUF_LEN];
int curlen = 0;int mychar_open(struct inode *pnode, struct file *pfile)
{printk("mychar_open\n");return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{printk("mychar_close\n");return 0;
}ssize_t mychar_read(struct file *pfile, char __user *puser, size_t count, loff_t *p_pos)
{int size = 0;int ret = 0;if(count > curlen){size = curlen;}else{size = count;}ret = copy_to_user(puser, mydev_buf, size);if(ret){printk("copy_to_user failed\n");return -1;}memcpy(mydev_buf, mydev_buf + size, curlen - size);curlen = curlen - size;return size;
}
ssize_t mychar_write(struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos)
{int size = 0;int ret = 0;if(count > BUF_LEN - curlen){size = BUF_LEN - curlen;}else{size = count;}ret = copy_from_user(mydev_buf + curlen, puser, size);if(ret){printk("copy_from_user failed\n");return -1;}curlen = count + size;return size;
}struct file_operations myops = {.owner = THIS_MODULE,.open = mychar_open,.release = mychar_close,.read = mychar_read,.write = mychar_write,
};int __init mychar_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);/*Apply for device number*/ret = register_chrdev_region(devno, mychar_num, "mychar");if(ret){ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");if(ret){printk("get devno failed\n");return -1;}printk("copy_to_user failed\n");major = MAJOR(devno);//Easy to miss *****}/*Assign the 'struct cdev' a set of operation functions*/cdev_init(&mydev, &myops);/*Add 'struct cdev' to the kernel's data structure*/mydev.owner = THIS_MODULE;cdev_add(&mydev, devno, mychar_num);//add to Hash.return 0;
}
void __exit mychar_exit(void)
{dev_t devno = MKDEV(major,minor);//printk("mychar will exit\n");unregister_chrdev_region(devno, mychar_num);
}
MODULE_LICENSE("GPL");module_init(mychar_init);
module_exit(mychar_exit);
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>int main(int argc, char *argv[])
{int fd = -1;char buf[8] = "";if(argc < 2){printf("The argument is too few\n");return 1;}//fd = open(argv[1], O_RDONLY);fd = open(argv[1], O_RDWR);if(fd < 0){printf("open %s failed\n", argv[1]);return 2;}write(fd, "hello", 6);read(fd, buf, 8);printf("buf = %s\n", buf);close(fd);fd = -1;return 0;
}
九、ioctl操作实现
已知成员的地址获得所在结构体变量的地址:container_of(成员地址,结构体类型名,成员在结构体中的名称)
```
long xxx_ioctl (struct file *filp, unsigned int cmd, unsigned long arg);
功能:对相应设备做指定的控制操作(各种属性的设置获取等等)
参数:
filp:指向open产生的struct file类型的对象,表示本次ioctl对应的那次open
cmd:用来表示做的是哪一个操作
arg:和cmd配合用的参数
返回值:成功为0,失败-1
```
cmd组成
1. dir(direction),ioctl 命令访问模式(属性数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
2. type(device type),设备类型,占据 8 bit,在一些文献中翻译为 “幻数” 或者 “魔数”,可以为任意 char 型字符,例如
‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
3. nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;
4. size,涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;
```c#define _IOC(dir,type,nr,size) (((dir)<<_IOC_DIRSHIFT)| \((type)<<_IOC_TYPESHIFT)| \((nr)<<_IOC_NRSHIFT)| \((size)<<_IOC_SIZESHIFT))/* used to create numbers */// 定义不带参数的 ioctl 命令#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)//定义带读参数的ioctl命令(copy_to_user) size为类型名#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))//定义带写参数的 ioctl 命令(copy_from_user) size为类型名#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))//定义带读写参数的 ioctl 命令 size为类型名#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))/* used to decode ioctl numbers */#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)```
还是要重现创建/dev/mydev文件
sudo rm -rf /dev/mydev
sudo rmmod mychar
make clean
make
sudo insmod mychar
cat /proc/devices | grep mychar
sudo chmod a+w /dev/mydev
ll /dev/mydev
gcc -o testmychar_app testmychar_app.c
./testmychar_app /dev/mydev
max len is 100
cur len is 6
buf = hello
book@100ask:~/Linux_4412/mydrivercode/char$ cat mychar.h
#ifndef MYCHAR_H
#define MYCHAR_H#include <linux/ioctl.h>#define MY_CHAR_MAGIC 'k'#define MYCHAR_IOCTL_GET_MAXLEN _IOR(MY_CHAR_MAGIC, 1, int*)
#define MYCHAR_IOCTL_GET_CURLEN _IOR(MY_CHAR_MAGIC, 2, int*)#endif
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include "mychar.h"#define BUF_LEN 100int major = 11;
int minor = 0;
int mychar_num = 1;struct mychar_dev
{struct cdev mydev;char mydev_buf[BUF_LEN];int curlen;
};struct mychar_dev gmydev;int mychar_open(struct inode *pnode, struct file *pfile)
{pfile->private_data = container_of(pnode->i_cdev, struct mychar_dev, mydev);printk("mychar_open\n");return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{printk("mychar_close\n");return 0;
}ssize_t mychar_read(struct file *pfile, char __user *puser, size_t count, loff_t *p_pos)
{struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;int size = 0;int ret = 0;if(count > pmydev->curlen){size = pmydev->curlen;}else{size = count;}ret = copy_to_user(puser, pmydev->mydev_buf, size);if(ret){printk("copy_to_user failed\n");return -1;}memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);pmydev->curlen = pmydev->curlen - size;return size;
}
ssize_t mychar_write(struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos)
{int size = 0;int ret = 0;struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;if(count > BUF_LEN - pmydev->curlen){size = BUF_LEN - pmydev->curlen;}else{size = count;}ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);if(ret){printk("copy_from_user failed\n");return -1;}pmydev->curlen += size;return size;
}long mychar_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{int __user *pret = (int *)arg;int maxlen = BUF_LEN;int ret = 0;struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;switch(cmd){case MYCHAR_IOCTL_GET_MAXLEN:ret = copy_to_user(pret, &maxlen, sizeof(int));if(ret){printk("copy_to_user MAXLEN failed\n");return -1;}break;case MYCHAR_IOCTL_GET_CURLEN:ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));if(ret){printk("copy_to_user MAXLEN failed\n");return -1;}break;default:printk("The cmd is unknow\n");return -1;}return 0;
}struct file_operations myops = {.owner = THIS_MODULE,.open = mychar_open,.release = mychar_close,.read = mychar_read,.write = mychar_write,.unlocked_ioctl = mychar_ioctl,
};int __init mychar_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);/*Apply for device number*/ret = register_chrdev_region(devno, mychar_num, "mychar");if(ret){ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");if(ret){printk("get devno failed\n");return -1;}printk("copy_to_user failed\n");major = MAJOR(devno);//Easy to miss *****}/*Assign the 'struct cdev' a set of operation functions*/cdev_init(&gmydev.mydev, &myops);/*Add 'struct cdev' to the kernel's data structure*/gmydev.mydev.owner = THIS_MODULE;cdev_add(&gmydev.mydev, devno, mychar_num);//add to Hash.return 0;
}
void __exit mychar_exit(void)
{dev_t devno = MKDEV(major,minor);//printk("mychar will exit\n");unregister_chrdev_region(devno, mychar_num);
}
MODULE_LICENSE("GPL");module_init(mychar_init);
module_exit(mychar_exit);
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include "mychar.h"
#include <sys/ioctl.h>int main(int argc, char *argv[])
{int fd = -1;char buf[8] = "";int max = 0;int cur = 0;if(argc < 2){printf("The argument is too few\n");return 1;}//fd = open(argv[1], O_RDONLY);fd = open(argv[1], O_RDWR);if(fd < 0){printf("open %s failed\n", argv[1]);return 2;}ioctl(fd, MYCHAR_IOCTL_GET_MAXLEN, &max);printf("max len is %d\n", max);write(fd, "hello", 6);ioctl(fd, MYCHAR_IOCTL_GET_CURLEN, &cur);printf("cur len is %d\n", cur);read(fd, buf, 8);printf("buf = %s\n", buf);close(fd);fd = -1;return 0;
}
十、printk
和printf差不多就是不能打印浮点型0-3是错误 4是警告 5-7是调试信息
错误分成四个等级0最严重,发生后系统就不能使用了。
```c//日志级别#define KERN_EMERG "<0>" /* system is unusable */#define KERN_ALERT "<1>" /* action must be taken immediately */#define KERN_CRIT "<2>" /* critical conditions */#define KERN_ERR "<3>" /* error conditions */#define KERN_WARNING "<4>" /* warning conditions */#define KERN_NOTICE "<5>" /* normal but significant condition */#define KERN_INFO "<6>" /* informational */#define KERN_DEBUG "<7>" /* debug-level messages */用法:printk(KERN_INFO"....",....)printk(KERN_INFO"Hello World"); =====> printk("<6>""Hello World") ====> printk("<6>Hello World")```
给程序员看的东西叫做热字
dmesg --level=emerg,alert,crit,err,warn,notice,info,debug
```c
#define HELLO_DEBUG
#undef PDEBUG
#ifdef HELLO_DEBUG
#define PDEBUG(fmt, args...) printk(KERN_DEBUG fmt, ##args)
#else
#define PDEBUG(fmt, args...)
#endif
```
十一、多个次设备的支持
每一个具体设备(次设备不一样的设备),必须有一个struct cdev来代表它
cdev_init
cdev.owner赋值
cdev_add
以上三个操作对每个具体设备都要进行
一份驱动代码可以支持多个同类次设备
一个具体的设备可以占用多个次设备号,主设备号是一个
我找到为什么每次都要创建/dev/mydev的原因了,我上面的程序中没有删除cdev所描述的设备只进行了释放
好吧插入后内核死了我真吐了。昨天没拍快照只能恢复到前天呜呜呜
make
gcc -o app testmychar.c
sudo mknod /dev/mydev0 c 11 0
sudo mknod /dev/mydev1 c 11 1
sudo mknod /dev/mydev2 c 11 2
sudo insmod multimychar.ko
cat /proc/devices | grep mychar
sudo chmod a+w /dev/mydev*
./app /dev/mydev0
./app /dev/mydev1
./app /dev/mydev2
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include "mychar.h"#define BUF_LEN 100#define MYCHAR_DEV_CNT 3/*Main devices number*/
int major = 11;
/*Secondary devices number*/
int minor = 0;
/*Number of supported devices*/
int mychar_num = MYCHAR_DEV_CNT;struct mychar_dev
{struct cdev mydev;char mydev_buf[BUF_LEN];int curlen;
};struct mychar_dev gmydev[MYCHAR_DEV_CNT];int mychar_open(struct inode *pnode, struct file *pfile)
{pfile->private_data = container_of(pnode->i_cdev, struct mychar_dev, mydev);printk("mychar_open\n");return 0;
}
int mychar_close(struct inode *pnode, struct file *pfile)
{printk("mychar_close\n");return 0;
}ssize_t mychar_read(struct file *pfile, char __user *puser, size_t count, loff_t *p_pos)
{struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;int size = 0;int ret = 0;if(count > pmydev->curlen){size = pmydev->curlen;}else{size = count;}ret = copy_to_user(puser, pmydev->mydev_buf, size);if(ret){printk("copy_to_user failed\n");return -1;}memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);pmydev->curlen = pmydev->curlen - size;return size;
}
ssize_t mychar_write(struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos)
{int size = 0;int ret = 0;struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;if(count > BUF_LEN - pmydev->curlen){size = BUF_LEN - pmydev->curlen;}else{size = count;}ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);if(ret){printk("copy_from_user failed\n");return -1;}pmydev->curlen += size;return size;
}long mychar_ioctl(struct file *pfile, unsigned int cmd, unsigned long arg)
{int __user *pret = (int *)arg;int maxlen = BUF_LEN;int ret = 0;struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;switch(cmd){case MYCHAR_IOCTL_GET_MAXLEN:ret = copy_to_user(pret, &maxlen, sizeof(int));if(ret){printk("copy_to_user MAXLEN failed\n");return -1;}break;case MYCHAR_IOCTL_GET_CURLEN:ret = copy_to_user(pret, &pmydev->curlen, sizeof(int));if(ret){printk("copy_to_user MAXLEN failed\n");return -1;}break;default:printk("The cmd is unknow\n");return -1;}return 0;
}struct file_operations myops = {.owner = THIS_MODULE,.open = mychar_open,.release = mychar_close,.read = mychar_read,.write = mychar_write,.unlocked_ioctl = mychar_ioctl,
};int __init mychar_init(void)
{int ret = 0;dev_t devno = MKDEV(major,minor);int i = 0;/*Apply for device number*/ret = register_chrdev_region(devno, mychar_num, "mychar");if(ret){ret = alloc_chrdev_region(&devno, minor, mychar_num, "mychar");if(ret){printk("get devno failed\n");return -1;}printk("copy_to_user failed\n");major = MAJOR(devno);//Easy to miss *****}for(i = 0; i < MYCHAR_DEV_CNT; i++){devno = MKDEV(major, minor+i);/*Assign the 'struct cdev' a set of operation functions*/cdev_init(&gmydev[i].mydev, &myops);/*Add 'struct cdev' to the kernel's data structure*/gmydev[i].mydev.owner = THIS_MODULE;cdev_add(&gmydev[i].mydev, devno, mychar_num);//add to Hash.}return 0;
}
void __exit mychar_exit(void)
{dev_t devno = MKDEV(major,minor);int i = 0;for(i = 0; i < MYCHAR_DEV_CNT; i++){/*Release device number*/cdev_del(&gmydev[i].mydev);}//printk("mychar will exit\n");unregister_chrdev_region(devno, mychar_num);
}
MODULE_LICENSE("GPL");module_init(mychar_init);
module_exit(mychar_exit);
相关文章:
字符设备驱动基础(一)
目录 一、Linux内核对设备的分类 linux的文件种类: Linux内核按驱动程序实现模型框架的不同,将设备分为三类: 总体框架图: 二、设备号------内核中同类设备的区分 三、申请和注销设备号 四、函数指针复习 4.1、 内存四区 …...
将 Supabase 作为下一个后端服务
对于想快速实现一个产品而言,如果使用传统开发,又要兼顾前端开发,同时又要花费时间构建后端服务。然而有这么一个平台(Baas Backend as a service)后端即服务,能够让开发人员可以专注于前端开发,…...
14:高级篇 - CTK 服务工厂 简述
作者: 一去、二三里 个人微信号: iwaleon 微信公众号: 高效程序员 一般情况下,服务对象在被注册之后,任何其它的 Plugin 在请求该服务时,CTK Plugin Framework 都返回的是同一个对象。倘若要为每一个 Plugin 消费者返回不同的服务对象,或者在真正需要该服务对象时才创建…...
Java中的链表实现介绍
Java中的链表实现介绍 学习数据结构的的链表和树时,会遇到节点(node)和链表(linked list)这两个术语,节点是处理数据结构的链表和树的基础。节点是一种数据元素,包括两个部分:一个是…...
演示Ansible中的角色使用方法(ansible roles)
文章目录一、ansible 角色简介二、roles目录结构三、role存放的路径:配置文件ansible.cfg中定义四、创建目录结构五、playbook中使用rolesplaybook变量会覆盖roles中的定义变量六、控制任务执行顺序七、ansible—galaxy命令工具八、安装选择的角色1.从网上下载&…...
Bash Shell 通过ls命令筛选文件
Bash Shell 通过ls命令及其管道根据大小名称筛选文件 最近参与的项目当中有需要用pyarmor加密项目的要求,听网上吹的pyarmor都那么神,用了一下感觉也一般,试用版普通模式下文件加密居然还有大小32KB的限制,加密到一半就失败了&am…...
2023-2-18 刷题情况
删列造序 III 题目描述 给定由 n 个小写字母字符串组成的数组 strs ,其中每个字符串长度相等。 选取一个删除索引序列,对于 strs 中的每个字符串,删除对应每个索引处的字符。 比如,有 strs [“abcdef”,“uvwxyz”] …...
【Linux】进程控制
文章目录进程创建简单认识一下fork()函数为什么fork()会有两个返回值fork通过写时拷贝的方式创建子进程进程终止进程退出码进程退出的方式exit()和_exit()进程等待进程等待方法 -- wait()和waitpid()status参数解释waitpid()的pid参数waitpid()的options参数 - 阻塞和非阻塞进程…...
谷歌seo快排技术怎么做?Google排名霸屏推广原理
本文主要分享关于谷歌快速排名的方法和所需要的条件。 本文由光算创作,有可能会被剽窃和修改,我们佛系对待这种行为吧。 首先提出一个问题:谷歌seo快排技术怎么做?如何达到谷歌霸屏的效果? 答案是:利用谷…...
MySQL的优化
目录 一.概念 二.查看SQL执行频率 三.定位低效率执行SQL 定位低效率执行SQL—慢查询日志 操作 定位低效率执行SQL—show processlist 四.explain分析执行计划 字段说明 explain中的id explain中的select_type explain中的type explain中的table explain中的rows ex…...
实现qq群消息接收和发送功能
QQWebsocketClient是什么 实现qq群消息接收和发送功能,基于websocket技术和cqhttp服务开发 一、 效果截图 二、实现思路 使用cqhttp进行socket反向代理,获取qq聊天的所有消息 编写java客户端,连接至cqhttp服务器获取聊天消息 获取聊天消…...
压缩20M文件从30秒到1秒的优化过程
压缩20M文件从30秒到1秒的优化过程 有一个需求需要将前端传过来的10张照片,然后后端进行处理以后压缩成一个压缩包通过网络流传输出去。之前没有接触过用Java压缩文件的,所以就直接上网找了一个例子改了一下用了,改完以后也能使用࿰…...
如何选择合适的固态继电器?
如何选择合适的固态继电器? 在选择固态继电器(SSR)时,应根据实际应用条件和SSR性能参数,特别要考虑到使用中的过流和过压条件以及SSR的负载能力,这有助于实现固态继电器的长寿命和高可靠性。然后࿰…...
SAP 忘记SAP系统Client 000的所有账号密码
忘记SAP系统Client 000的所有账号密码。 Solution 在SAP系统DB中删除账号SAP*,SAP系统会自动创建SAP*这个账号,然后初始密码是“PASS”,这样就获得Client 000 SAP*账号。 Step by Step 以Oracle数据库为例: 1.以<SID>ADM账…...
Connext DDS可扩展类型Extensible Types指南
RTI Connext DDS 可扩展类型Extensible Types指南 可扩展类型Extensible TypesConnextDDSv6.1.1版本,包含了对OMG“DDS的可扩展和动态主题类型Extensible andDynamic Topic Types for DDS”规范1.3版的部分支持,该规范来自对象管理组OMG。这种支持,允许系统以更灵活的方式定义…...
Docker简单使用
文章目录1、安装配置2、服务启动3、Docker镜像下载4、Docker启动容器5、容器的常用命令6、Docker进入容器内部7、宿主机与容器交换文件8、查看日志官网地址:1、安装配置 sudo yum install -y yum-utils 设置镜像地址 sudo yum-config-manager \--add-repo \https:…...
A Time Series is Worth 64 Words(PatchTST模型)论文解读
摘要 我们提出了一种高效的基于Transformer设计的模型,用于多变量时间序列预测和自我监督表征学习(self-supervised learning)。它基于两个关键部分:1、将时间序列分隔成子序列级别的patches,作为Transformer的输入&a…...
微服务学习:SpringCloud+RabbitMQ+Docker+Redis+搜索+分布式
目录 一、高级篇 二、面试篇 实用篇 day05-Elasticsearch01 安装elasticsearch 1.部署单点es 2.部署kibana 一、高级篇 二、面试篇 实用篇 day05-Elasticsearch01 安装elasticsearch 1.部署单点es 1.1.创建网络 因为我们还需要部署kibana容器,因此需要…...
nginx平滑升级
1.平滑升级操作1.1 备份安装目录下的nginxcd /usr/local/nginx/sbin mv nginx nginx.bak1.2 复制objs目录下的nginx到当前sbin目录下cp /opt/software/nginx/nginx-1.20.2/objs/nginx /usr/local/nginx/sbin/1.3 发送信号user2给nginx老版本对应的进程kill -user2 more /usr/lo…...
高可用的“异地多活”架构设计
前言 后台服务可以划分为两类,有状态和无状态。高可用对于无状态的应用来说是比较简单的,无状态的应用,只需要通过 F5 或者任何代理的方式就可以很好的解决。后文描述的主要是针对有状态的服务进行分析。 服务端进行状态维护主要是通过磁盘…...
【面试题】Map和Set
1. Map和Object的区别 形式不同 // Object var obj {key1: hello,key2: 100,key3: {x: 100} } // Map var m new Map([[key1, hello],[key2, 100],[key3, {x: 100}] ])API不同 // Map的API m.set(name, 小明) // 新增 m.delete(key2) // 删除 m.has(key3) // …...
Spring之事务底层源码解析
Spring之事务底层源码解析 1、EnableTransactionManagement工作原理 开启 Spring 事务本质上就是增加了一个 Advisor,当我们使用 EnableTransactionManagement 注解来开启 Spring 事务时,该注解代理的功能就是向 Spring 容器中添加了两个 Bean…...
【华为OD机试真题 Python】创建二叉树
前言:本专栏将持续更新华为OD机试题目,并进行详细的分析与解答,包含完整的代码实现,希望可以帮助到正在努力的你。关于OD机试流程、面经、面试指导等,如有任何疑问,欢迎联系我,wechat:steven_moda;email:nansun0903@163.com;备注:CSDN。 题目描述 请按下列描达构建…...
RuoYi-Vue-Plus搭建(若依)
项目简介 1.RuoYi-Vue-Plus 是重写 RuoYi-Vue 针对 分布式集群 场景全方位升级(不兼容原框架)2.环境安装参考:https://blog.csdn.net/tongxin_tongmeng/article/details/128167926 JDK 11、MySQL 8、Redis 6.X、Maven 3.8.X、Nodejs > 12、Npm 8.X3.IDEA环境配置…...
uboot和linux内核移植流程简述
一、移植uboot流程 1、从半导体芯片厂下载对应的demo,然后编译测试demo版的uboot 开发板基本都是参考半导体厂商的 dmeo 板,而半导体厂商会在他们自己的开发板上移植好 uboot、linux kernel 和 rootfs 等,最终制作好 BSP包提供给用户。我们可…...
【CS224W】(task2)传统图机器学习和特征工程
note 和CS224W课程对应,将图的基本表示写在task1笔记中了;传统图特征工程:将节点、边、图转为d维emb,将emb送入ML模型训练Traditional ML Pipeline Hand-crafted feature ML model Hand-crafted features for graph data Node-l…...
【算法基础】并查集⭐⭐⭐⭐⭐【思路巧,代码短,面试常考】
并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中。其特点是看似并不复杂,但数据量…...
人工智能轨道交通行业周刊-第34期(2023.2.13-2.19)
本期关键词:智慧地铁、枕簧检测选配机器人、智慧工地、接触网检修、工业缺陷检测 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro…...
Retrofit 网络框架源码解析(二)
目录一、Okhttp请求二、Retrofit 请求retrofit是如何封装请求的三、Retrofit的构建过程四、Retrofit构建IxxxService对象的过程(Retrofit.create())4.1 动态代理4.2 ServiceMethod4.3 okHttpCall4.4 callAdapter五、Retrofit网络请求操作一、Okhttp请求 …...
SQL Server 2008新特性——更改跟踪
在大型的数据库应用中,经常会遇到部分数据的脱机和多个数据库的合并问题。比如现在有一个全省范围使用的应用程序,每个市都部署了单独的相同的应用程序服务器和数据库服务器,每个月需要将全省所有市的数据全部汇总起来用于出全省的报表&#…...
vps建立多个网站/广东又出现新病毒
研究了一下,并和开发这探讨了一下这个扩展,扩展会不断更新,作者也在努力出个windows版的啊 扩展开源地址:https://code.google.com/p/php-beast/downloads/detail?namebeast-v0.2.zip&can2&q转载于:https://www.cnblogs.…...
网站门户是什么意思/百度没有排名的点击软件
两个可能的病毒现象求助!一 我在公司局域网上的计算机近来发现启动IE或者其他程序明显变慢,后检查发现如果关掉网络连接就正常了,打开连接后问题又出现了,不知何故,如何解决?(win2k sp4)二 我的家里的计算机…...
专做自驾游的网站/今天重大新闻头条
. Word 文档 程序设计 题目:补充 fun 函数,其功能是判断一个整数是否是素数,在主 函数 main 中输入一个整数,调用该 fun 函数进行判断并输 出结果。 要求:使用 math 相关函数 import math def fun(n): i,w2,0 if n<…...
建设移动端网站/seo刷排名公司
SQL Server 2005 数据库引擎对象最大大小/数量 SQL Server 2005(32 位)最大大小/数量 SQL Server 2005(64 位) 批大小1 65,536 * 网络数据包大小 65,536 * 网络数据包大小 每个短字符串列的字节数 8,…...
网站开发公司地址/广州市新闻发布
题库来源:安全生产模拟考试一点通公众号小程序 2021年流动式起重机司机报名考试及流动式起重机司机考试软件,包含流动式起重机司机报名考试答案和解析及流动式起重机司机考试软件练习。由安全生产模拟考试一点通公众号结合国家流动式起重机司机考试最新…...
http做轮播图网站/中文搜索引擎排行榜
前面写好了数据访问工程DAL,但由于用户界面也要访问数据还有中间业务层也访问,为了访问方便还要处理一些业务逻辑,我们再给它封装一层DALService来操作DAL和对上层提供服务。 添加一个工程DALService,分别对DAL里面的每个类提…...