>  기사  >  Linux의 문자 장치는 무엇입니까?

Linux의 문자 장치는 무엇입니까?

青灯夜游
青灯夜游원래의
2023-03-09 10:49:021939검색

Linux 문자 장치에는 다음이 포함됩니다. 1. 컴퓨터의 외부 입력 장치이자 컴퓨터 디스플레이 시스템의 수직 및 수평 좌표 위치를 지정하는 표시기인 마우스 2. 다음 작업에 사용되는 명령 및 데이터 입력 장치인 키보드 3. 직렬 포트 터미널, 컴퓨터 직렬 포트를 사용하여 연결된 터미널 장치 5. 콘솔 등

Linux의 문자 장치는 무엇입니까?

이 튜토리얼의 운영 환경: linux7.3 시스템, Dell G3 컴퓨터.

linux 문자 장치


문자 장치는 Linux의 세 가지 주요 장치 중 하나입니다(나머지 두 가지는 블록 장치와 네트워크 장치입니다). 이들은 모두 파일 노드의 형태로 파일 시스템의 /dev 디렉토리에 표시됩니다(crw--w---- 1 root tty 4, 0 July 11 09:11 tty0 여기서 c는 문자 장치 유형을 나타냄).

문자 장치란 마우스, 키보드, 직렬 포트 장치, 모뎀 등 버퍼링 없이 직접 읽고 쓸 수 있는 장치를 말합니다. 문자 연산의 기본 단위가 바이트라는 점에서 블록 장치와 차이점이 있습니다.

문자 장치 분류

문자 장치에는 주로 콘솔, 키보드 등 제어 단말 장치와 직렬 단말 장치가 포함됩니다. 기능과 하드웨어의 차이에 따라 문자 단말 장치는 다음과 같이 분류됩니다.

  • 직렬 포트 터미널(/dev/ttSn): 컴퓨터 직렬 포트를 사용하여 연결된 단말 장치 직렬 장치 데이터 전송 방법은 동일합니다. 8비트는 한 줄로 전송됩니다. 명령줄에 echo 'hello world' > /dev/ttyS0을 입력하면 해당 장치에 입력이 기록됩니다.

  • 의사 터미널(/dev/ttyp, /dev/ptyp): 하위 계층에 실제 하드웨어 장치가 없다는 사실에 맞춰, 다음과 같은 다른 프로그램에 터미널 스타일 인터페이스를 제공하는 데 사용됩니다. 네트워크를 통해 호스트에 로그인할 때 네트워크 서버와 쉘 프로그램 사이.

  • 제어 터미널(/dev/tty): 기본 장치 번호는 5입니다. 프로세스 제어 터미널은 프로세스와 연결됩니다. 예를 들어 로그인 쉘 프로세스는 /dev/tty 터미널을 사용합니다.

  • 콘솔(/dev/ttyn, /dev/consol): 컴퓨터 입출력을 위한 모니터입니다. 콘솔에 로그인하면 tty1이 사용되고, ubuntu 그래픽 인터페이스는 tty7을 사용합니다.

  • 기타 유형: 현재 Linux에는 ISIDIN 장치의 /dev/ttyIn 장치와 같이 다양한 장치에 대한 다양한 유형의 장치 특수 파일이 있습니다.

캐릭터 디바이스의 본질과 특성

  • 캐릭터 디바이스는 디바이스 파일 시스템의 일종으로, 연결과 마찬가지로 상위 계층에 기본 하드웨어가 제공하는 논리적 디바이스 파일과 동일합니다. 데이터 포트(데이터 레지스터) 파일이 연결되면 장치 드라이버가 파일에 대해 직접 동작하므로 포트에 대한 읽기 및 쓰기 작업을 직접 수행합니다. 또한 파일로서 문자 장치 드라이버는 open(), close(), write(), read() 등과 같은 파일의 기본 작업도 구현해야 합니다. 물론 터미널 리디렉션 작업도 지원됩니다.

  • 캐릭터 디바이스 파일 파일은 싱글바이트 단위로 읽고 쓰기 때문에 하드웨어 버퍼를 설정할 필요가 없습니다. 장치는 운영 체제에서 바이트 스트림으로 액세스됩니다. 바이트 스트림은 하드웨어 포트와 파일 시스템 사이에 전송 파이프를 설정하는 것과 같습니다. 바이트는 파이프를 통해 하나씩 전송되어 판독기와 기록기 모두에게 제공됩니다. 이 스트리밍 기능은 드라이버에서 버퍼 대기열로 구현됩니다. 예: 콘솔 구조의 읽기-쓰기 버퍼 큐

struct tty_struct {
struct termios termios;
int pgrp;
int stopped;
void (*write)(struct tty_struct * tty);
struct tty_queue read_q;               //读队列
struct tty_queue write_q;              //写队列
struct tty_queue secondary;            //tty辅助队列(存放规格化后的字符)
};
  • 문자 장치는 문자 장치 번호로 식별됩니다. 문자 장치 번호는 주요 장치 번호와 부 장치 번호로 구성됩니다. 예를 들어, /dev/ttyS0의 장치 번호는 (4, 64)이며, 커널은 해당 장치에 해당하는 드라이버를 식별합니다. 일반적으로 부 장치 번호는 장치와 드라이버를 일대일로 일치시키기 위해 드라이버에서 내부적으로 사용하는 코드로 사용되며 다른 부분에서는 사용되지 않습니다. 커널의.

어플리케이션 계층에 캐릭터 디바이스가 반영되는 방식

cat /proc/devices 명령을 사용하면 현재 시스템의 모든 캐릭터 디바이스와 블록 디바이스를 볼 수 있습니다.

Linux의 문자 장치는 무엇입니까?

Linux의 문자 장치는 무엇입니까?

Linux에서는 파일 연결 시 장치도 파일로 추상화됩니다. 문자 장치와 블록 장치에 해당하는 파일은 /dev/ 디렉터리에서 볼 수 있습니다.
예를 들어 다음은 두 개의 직렬 포트 장치 파일입니다. ls -l을 사용하여 세부 정보를 확인하세요.
일반 파일과 달리 장치 파일에는 크기가 없지만 장치 번호(주 장치 번호 및 보조 장치 번호)로 대체됩니다. 장치 번호는 /proc/devices의 정보에 해당할 수 있습니다.

Linux의 문자 장치는 무엇입니까?

기기에 액세스하는 방법은 무엇인가요?

파일로 추상화되어 있기 때문에 파일 IO(열기, 읽기, 쓰기 등)를 사용하여 접근하는 것이 당연합니다.

设备号

Linux의 문자 장치는 무엇입니까?

dev_t dev = MKDEV(major,minor)
major = MAJOR(dev)
minor = MINOR(dev)

设备号由major和minor 组成,总共32位,高12位为major,低20位为minor。每一个设备号都唯一对应着一个cdev结构体。

注册字符设备首先要申请设备号,可以一次批量的申请多个设备号(相同的major,依次递增的minor)。
如果没有指定主设备号的话就使用如下函数来申请设备号:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
baseminor:起始的minor值。
count:一共申请几个设备号。申请到的设备号在(MKDEV(major,baseminor) ~ MKDEV(major,baseminor+count)) 范围内。
(自动申请设备号的原理,其实是传递一个major = 0,然后由内核分配一个空闲的设备号返回)

如果给定了设备的主设备号和次设备号就使用如下所示函数来注册设备号即可:
int register_chrdev_region(dev_t from, unsigned count, const char *name)

释放设备号:
void unregister_chrdev_region(dev_t from, unsigned count)

字符设备结构体 cdev

//include/linux/cdev.h
struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;
        struct list_head list;
        dev_t dev;
        unsigned int count;
};

常用

申请一个cdev 内存:
struct cdev *cdev_alloc(void);
初始化cdev->ops,即cdev->ops = &xxx_file_operation; :
void cdev_init(struct cdev *, const struct file_operations *);
将填充好的cdev 实例,添加到cdev 链表。意味着向内核注册一个字符设备:
int cdev_add(struct cdev *, dev_t, unsigned); //dev_t:添加cdev时必须要,传递一个dev_t,并且它与cdev是唯一对应的。
cdev_add 添加过程中会绑定cdev 与dev_t。

从内核删除一个字符设备:
void cdev_del(struct cdev *);

不常用
增加cdev 调用计数:
void cdev_put(struct cdev *p);

总结:注册字符设备的主要流程就是,申请设备号dev_t,创建一个cdev、初始化cdev (cdev->ops)、向内核添加 cdev(同时会绑定cdev 与dev_t)。

如果你嫌这些步骤太麻烦的话,内核还提供了一个函数可以一步到位的注册字符设备——__register_chrdev
它会申请多个设备号,创建cdev并初始化它,然后用这多个设备号绑定同一个cdev 注册。

Linux의 문자 장치는 무엇입니까?

还有一个register_chrdev,它是对__register_chrdev 的封装,次设备号从基值0开始,直接申请了256 个次设备号。
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops) { return __register_chrdev(major, 0, 256, name, fops); }

struct file_operations

字符设备在/dev/ 目录下创建设备文件,并通过struct file_operations 向应用层提供控制接口。应用层对应的open、read 等函数会调用到file_operations 对应的函数。

//include/linux/fs.h
struct file_operations {
        struct module *owner;
        loff_t (*llseek) (struct file *, loff_t, int);
        ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
        ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
        ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
        ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
        int (*iterate) (struct file *, struct dir_context *);
        unsigned int (*poll) (struct file *, struct poll_table_struct *);
        long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
        long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
        int (*mmap) (struct file *, struct vm_area_struct *);
        int (*mremap)(struct file *, struct vm_area_struct *);
        int (*open) (struct inode *, struct file *);
        int (*flush) (struct file *, fl_owner_t id);
        int (*release) (struct inode *, struct file *);
        int (*fsync) (struct file *, loff_t, loff_t, int datasync);
        int (*aio_fsync) (struct kiocb *, int datasync);
        int (*fasync) (int, struct file *, int);
        int (*lock) (struct file *, int, struct file_lock *);
        ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
        unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
        int (*check_flags)(int);
        int (*flock) (struct file *, int, struct file_lock *);
        ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
        ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
        int (*setlease)(struct file *, long, struct file_lock **, void **);
        long (*fallocate)(struct file *file, int mode, loff_t offset,
                          loff_t len);
        void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
        unsigned (*mmap_capabilities)(struct file *);
#endif
};

copy_to_user() 与 copy_from_user()

为了安全考虑,应用进程不能直接访问内核数据,需要借助这两个函数拷贝:
static inline int copy_to_user(void __user volatile *to, const void *from, unsigned long n)
static inline int copy_from_user(void *to, const void __user volatile *from, unsigned long n)
返回非0 表示错误。

自动创建设备文件

自动创建设备节点的工作是在驱动程序的入口函数中完成的,一般在 cdev_add 函数后面添加自动创建设备节点相关代码。首先要创建一个 class 类, class 是个结构体,定义在文件include/linux/device.h 里面。

使用 class_create 创建一个类:

extern struct class * __must_check __class_create(struct module *owner,
                                                  const char *name,
                                                  struct lock_class_key *key);

#define class_create(owner, name)               \
({                                              \
        static struct lock_class_key __key;     \
        __class_create(owner, name, &__key);    \
})

使用class_destroy 摧毁一个类:
extern void class_destroy(struct class *cls);

struct class {
        const char              *name;
        struct module           *owner;

        struct class_attribute          *class_attrs;
        const struct attribute_group    **dev_groups;
        struct kobject                  *dev_kobj;

        int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);
        char *(*devnode)(struct device *dev, umode_t *mode);

        void (*class_release)(struct class *class);
        void (*dev_release)(struct device *dev);

        int (*suspend)(struct device *dev, pm_message_t state);
        int (*resume)(struct device *dev);

        const struct kobj_ns_type_operations *ns_type;
        const void *(*namespace)(struct device *dev);

        const struct dev_pm_ops *pm;

        struct subsys_private *p;
};

在创建完类后,要创建一个设备,使用 device_create创建一个设备:
struct device *device_create(struct class *cls, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);

摧毁一个设备:
extern void device_destroy(struct class *cls, dev_t devt);

创建类会在/sys/class/ 目录下生成一个新的文件夹,其中包含属于此类的设备文件夹。

5-Linux의 문자 장치는 무엇입니까?

IS_ERR 和 PTR_ERR

IS_ERR 可以判断一个指针是否为空,PTR_ERR 将指针转化为数值返回。

static inline long __must_check PTR_ERR(const void *ptr)
{
	return (long) ptr;
}

static inline long __must_check IS_ERR(const void *ptr)
{
	return IS_ERR_VALUE((unsigned long)ptr);
}

代码示例

#include <linux/fs.h>		 //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>	 //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件

#define DEVICE_CNT 0	//设备号个数

struct led_device{
	dev_t devid;	//设备号
	int major;	//主设备号
	int minor;	//次设备号
	char* name = "led";	//驱动名
	struct cdev led_dev;	//cdev 结构体
	struct class *class;	/* 类 	*/
	struct device* device;	//设备
};

struct led_device led;


static int led_open(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t led_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}


static ssize_t led_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	return 0;
}



/* 设备操作函数 */
static struct file_operations led_fo = {
	.owner = THIS_MODULE,
	.open = led_open,
	.read = led_read,
	.write = led_write,
};

static int _init led_init()
{
	/*注册字符设备驱动*/
	
	/*1.注册设备号*/
	led.major = 0;	//由内核自动分配主设备号
	if(led.major)	//如果分配了的话就注册
	{
		led.devid = MKDEV(led.major,0);	
		register_chrdev_region(led.devid,DEVICE_CNT,led.name);	//将驱动注册到内核中
	}
	else{		//如果没有分配的话
					//从0号(次设备号)开始申请
		alloc_chrdev_region(&led.devid,0,DEVICE_CNT,led.name);		//申请设备号
		led.major = MAJOR(led.devid);	//获取主设备号
		led.minor = MANOR(led.devid);	//获取次设备号
	}
	printk("newcheled major=%d,minor=%d\r\n",newchrled.major, newchrled.minor);	

	/*2.初始化 cdev 结构体*/
	led.led_dev.woner = THIS_MODULE;
	cdev_init(&led.led_dev,&led_fo);	//将操作函数初始化到cdev结构体

	/*3.应该是向链表中添cdev*/
	cdev_add(&led.led_dev,led.devid,DEVICE_CNT);	

	/*4.创建节点*/
	led.class = class_create(THIS_MODULE,led.name);		//先创建一个类
	led.device = device_create(led.class,NULL,led.devid,NULL);	//创建设备

	return 0;
		
}

static void _exit led_exit()
{
	/* 注销字符设备驱动 */
	cdev_del(&newchrled.cdev);/*  删除cdev */
	unregister_chrdev_region(newchrled.devid, NEWCHRLED_CNT); /* 注销设备号 */

	device_destroy(newchrled.class, newchrled.devid);
	class_destroy(newchrled.class);
}



/*注册字符设备入口与卸载入口*/
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("zhoujianghong");

应用open到file_operations->open 的调用原理

Linux의 문자 장치는 무엇입니까?

相关推荐:《Linux视频教程

위 내용은 Linux의 문자 장치는 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.