Skip to content

Commit b0c5d47

Browse files
committed
learning globalmem
1 parent 6509965 commit b0c5d47

File tree

2 files changed

+151
-0
lines changed

2 files changed

+151
-0
lines changed
122 KB
Loading

为了工作/Linux/Linux 设备驱动开发详解.md

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1772,3 +1772,154 @@ struct file_operations {
17721772

17731773
在模块加载函数中应实现设备号的申请和 cdev 的注册,在模块卸载函数中应实现 cdev 的注销和设备号的释放。
17741774

1775+
类似的模板如下:
1776+
1777+
```c
1778+
// 设备结构体
1779+
struct xxx_dev_t
1780+
{
1781+
struct cdev cdev;
1782+
1783+
...
1784+
} xxx_dev;
1785+
1786+
1787+
// 设备驱动模块加载函数
1788+
static int __init xxx_init(void)
1789+
{
1790+
...
1791+
1792+
cdev_init(&xxx_dev.cdev, &xxx_fops); // 初始化cdev
1793+
xxx_dev.cdev.owner = THIS_MODULE;
1794+
1795+
// 获取字符设备号
1796+
if (xxx_major)
1797+
{
1798+
register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
1799+
}
1800+
else
1801+
{
1802+
alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
1803+
}
1804+
1805+
res = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); // 注册设备
1806+
1807+
...
1808+
}
1809+
1810+
// 设备驱动模块卸载函数
1811+
static void __exit xxx_exit(void)
1812+
{
1813+
unregister_chrdev_region(xxx_dev_no, 1); // 释放占用的设备号
1814+
cdev_del(&xxx_dev.cdev); // 注销设备
1815+
1816+
...
1817+
}
1818+
```
1819+
1820+
#### file_operations 结构体的成员函数
1821+
1822+
file_operations 结构体的成员函数是字符设备驱动与内核虚拟文件系统的接口,是用户空间对 Linux 系统调用最终的落实者。
1823+
1824+
```c
1825+
// 读设备
1826+
// filp:文件结构体指针,buf:用户空间内存地址;count:要读的字节数;f_pos:读的位置相对文件开头的偏移。
1827+
ssize_t xxx_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
1828+
{
1829+
...
1830+
1831+
copy_to_user(buf, ..., ...);
1832+
1833+
...
1834+
}
1835+
1836+
// 写设备
1837+
// filp:文件结构体指针,buf:用户空间内存地址;count:要写的字节数;f_pos:读的位置相对文件开头的偏移。
1838+
ssize_t xxx_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
1839+
{
1840+
...
1841+
1842+
copy_from_user(..., buf, ...);
1843+
1844+
...
1845+
}
1846+
1847+
// ioctl 函数
1848+
// cmd 参数为事先定义的 I/O 控制命令,而 arg 为对应于该命令的参数。
1849+
// 例如对于串行设备,若 SET_BAUDRATE 是一道设置波特率的命令,arg 就应该是波特率值。
1850+
long xxx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
1851+
{
1852+
...
1853+
1854+
switch (cmd)
1855+
{
1856+
case XXX_CMD1:
1857+
{
1858+
...
1859+
1860+
break;
1861+
}
1862+
case XXX_CMD2:
1863+
{
1864+
...
1865+
1866+
break;
1867+
}
1868+
default:
1869+
// 不能支持的命令
1870+
return -ENOTTY;
1871+
}
1872+
1873+
1874+
return 0;
1875+
}
1876+
```
1877+
1878+
由于**用户空间不能直接访问内核空间的内存**,因此借助了函数 copy_from_user() 完成用户空间缓冲区到内核空间的复制,以及 copy_to_user() 完成内核空间到用户空间缓冲区的复制。这里注意一下语义,以读为例,注意主谓。xxx_read() 的含义是从内核空间中读,因此用户空间是接收的对象,因此使用 copy_to_user(),对应的也是用户空间的系统调用 read()。整个流程就是内核读 I/O(先不考虑用户区缓冲的事情),然后 copy_to_user() 到用户空间的内存。
1879+
1880+
这两个函数的声明如下:
1881+
1882+
```c
1883+
// 两个函数均返回不能被复制的字节数。如果成功,返回 0;如果失败,返回负值。
1884+
int copy_from_user(void *to, const void __user volatile *from, unsigned long n);
1885+
int copy_to_user(void __user volatile *to, const void *from, unsigned long n);
1886+
```
1887+
1888+
如果复制的内存是简单类型,如 char、int、long 等,可以使用简化的 put_user() 和 get_user():
1889+
1890+
```c
1891+
int val; // 内核空间整型变量。
1892+
1893+
get_user(val, (int *) arg); // 用户 -> 内核,arg 是用户空间的地址。
1894+
1895+
put_user(val, (int *) arg); // 内核 -> 用户,arg 是用户空间的地址。
1896+
```
1897+
1898+
上面读和写函数的参数中 `__user` 是一个宏,代表后面的指针属于用户空间,用于假注释和提醒的作用。类似于模块加载函数的 `__init` 和模块卸载函数的 `__exit`
1899+
1900+
内核空间虽然可以访问用户空间的数据,但是在访问之前,一般需要**检查合法性**。使用 `access_ok(addr, size)` 接口,以保证传入的数据的确属于用户空间。**此宏函数返回非 0 表示检查通过。**
1901+
1902+
另外,`put_user()``__put_user()` 的区别在于,`put_user()` 的实现中调用了 `__put_user()`,并且已经手动做了 `access_ok()` 的检查。所以一般推荐使用 `put_user()``get_user()`。同时,也存在 `copy_from_user()``__copy_from_user()`,区别同上。`copy_to_user()` 同理。但是,总的来讲为了保险,内核空间在访问用户空间数据以前,我们的驱动程序中应再次手动检查一下合法性。
1903+
1904+
在字符设备驱动中,需要定义一个 file_operations 的实例,将我们自己实现的函数注册给这些成员:
1905+
1906+
```c
1907+
// xxx_fops 在 cdev_init(&xxx_dev.cdev, &xxx_fops) 时同字符设备建立起连接。
1908+
struct file_operations xxx_fops = {
1909+
.owner = THIS_MODULE,
1910+
.read = xxx_read,
1911+
.write = xxx_write,
1912+
.unlocked_ioctl = xxx_ioctl,
1913+
1914+
...
1915+
};
1916+
```
1917+
1918+
下面是字符设备驱动的结构示意图:
1919+
1920+
<img src="https://img-blog.csdnimg.cn/direct/f0c4207c95a14c3bb342970f131afd7e.png" alt="image-20241108100951215" style="zoom:75%;" />
1921+
1922+
## globalmem 虚拟设备
1923+
1924+
globalmem 意为全局内存。在 globalmem 字符设备驱动中会分配一片大小为 GLOBALMEM_SIZE(4 KB) 的内存空间,并在驱动中提供针对该片内存的读写、控制和定位函数,以供用户空间的进程能通过 Linux 系统调用获取或设置这片内存的内容。
1925+

0 commit comments

Comments
 (0)