Понимание loff_t * offp для file_operations
Я разрабатываю драйвер устройства, который просто читает и записывает в буфер символов. Однако мой вопрос касается двух функций в структуре file_operations
read
и write
. Я действительно не понимаю, что такое loff_t *offp
. Я знаю, что для операций чтения и записи, что *offp
- это смещение файла, означающее текущую позицию чтения/записи файла, однако я даже не уверен, что это означает для записи или чтения/из файла устройства.
Из того, что я собрал, так я пишу и читаю с моего устройства, что я создаю структуру, которая представляет мое устройство, которое я называю my_char_struct
, которое показано ниже.
struct my_char_structure{
struct cdev my_cdev;
struct semaphore sem;
char *data;
ssize_t data_size;
unsigned int access_key;
unsigned long size;
};
Это статическая структура, которая инициализируется и указывается, когда мой драйвер insmod
как таковой.
static dev_t dev_num;
static struct my_char_structure Dev;
int start_mod(void){
//Because we are dealing with a fictitious device, I want
//the driver to create my two devices with arbitrarily
//assigned major numbers.
struct my_char_structure *my_dev = &Dev;
int err;
alloc_chrdev_region(&dev_num, FIRST_MINOR, COUNT, DEVICE_NAME);
sema_init(&(my_dev->sem),1);
cdev_init(&(my_dev->my_cdev), &fops);
my_dev->my_cdev.owner = THIS_MODULE;
my_dev->my_cdev.ops = &fops;// fops is my file operations struct
err = cdev_add(&my_dev->my_cdev, dev_num, COUNT);
if(err<0)
printk(KERN_ALERT "There was an error %d.",err);
printk(KERN_ALERT " insmod to major number %d",MAJOR(dev_num));
return 0;
}
module_init(start_mod);
Когда мое устройство открыто, я просто создаю указатель для открытого файла, чтобы указать на ту статическую структуру, которую я установил во время module_init(start_mod)
как таковой...
int dev_open(struct inode *in_node, struct file *filp){
static struct my_char_structure *my_dev;
my_dev = container_of(in_node->i_cdev, struct my_char_structure, my_cdev);
printk(KERN_ALERT "The device number is %d",iminor(in_node));
if(!my_dev)
printk(KERN_ALERT "something didn't work. my_dev not initialized.");
filp->private_data = my_dev;
return 0;
}
То, что делают мои методы чтения и записи, - это изменить исходную структуру Dev, на которую я указал мои открытые файлы. Независимо от того, что я copy_to_user
из моей структуры является тем, что пользователь считает записанным на устройстве, и каким бы я ни был copy_from_user
, пользователь думает, что пишет. Но, не изменяя мою первоначальную структуру Dev, идея позиции файла или смещения не имеет смысла, если только он не ссылается на указатель на буферную память в ядре для какой-либо произвольной структуры или типа. Это единственная интерпретация, которая у меня есть для смещения файла... это правильно? Это то, что здесь означает loff_t *offp
?
write(struct file *filp, const char __user *buff, size_t count, loff_t *offp)
read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
(при условии, что мое понимание верное) Когда вызывается некоторая файловая операция, такая как чтение/запись, и я не установил *offp
лично, что изначально установлено в loff_t * offp?
Если в последнем файле file_operation offp = some_arbitrary_address (потому что я так сказал), это то, что будет выбрано offp, когда эта операция будет вызвана снова?
Что произойдет, если у меня будут запущены другие операции с файлами file_opens, будет ли он установлен на то, что последний файл_операции оставил его как, или он сохранит вкладку, в которой она используется file_open, и заменит * offp на то, что у файла file_open?
Концепция устройства char слишком абстрактна для меня, когда кажется, что само устройство даже не хранит информацию, как файл, а скорее ее драйвер, который сохраняет информацию. Надеюсь, я объяснил свою туманность, и я проясню все, что мне кажется двусмысленным.
Ответы
Ответ 1
"loff_t" - это "длинное смещение", т.е. позиция поиска, которая объединяет сумасшедшее разнообразие off_t
, off64_t
и т.д., так что драйверы могут просто использовать loff_t и не беспокоиться об этом.
Указатель сам в момент, когда вы попадаете в драйвер, указывает на смещение, предоставляемое пользователем (при условии, что код пользователя делает доступ к драйверу - технически ядро может предоставить свое собственное, но пользовательский случай является тем, подумайте) через lseek
или llseek
или lseek64
и т.д., а затем обычными операциями чтения и записи. Рассмотрим случай регулярного файла на диске: когда вы первый open
файл, вы (как пользователь) получаете ядро для предоставления структуры данных, которая отслеживает вашу текущую позицию в файле, так что если вы read
или write
несколько байтов, следующий read
или write
выбирается с того места, где вы остановились.
Кроме того, если вы dup
файловый дескриптор или выполняете эквивалент (например, fork
и exec
) в терминах запуска последовательности команд, эта позиция поиска разделяется всеми процессами наследования. Следовательно, в командной строке командной строки:
(prog1; prog2; prog3) > outputfile
создает выходной файл, затем dup
дескриптор для трех программ, так что вывод, который записывает prog2
, записывается в файл сразу после выхода из prog1
, а вывод из prog3
следует за двумя другими Поскольку все три отдельных процесса имеют одну и ту же базовую структуру данных ядра с тем же внутренним loff_t
.
То же самое относится к файлам драйверов устройств. Когда вы вызываете функции чтения и записи, вы получаете "текущее смещение", как предоставлено пользователем, и вы можете (и должны) обновлять его по мере необходимости... при условии, что есть какая-либо необходимость (например, вы хотите предоставить пользователям появление обычного файла, в том числе тот факт, что поиск смещений перемещается по мере чтения и записи). Если устройство имеет некоторое логическое применение смещения поиска, вы можете использовать его здесь.
Конечно, для драйверов устройств гораздо больше, поэтому на этом материале есть целые книги-главы (q.v.).: -)
Ответ 2
Ответ Torek превосходный. Просто добавив немного дополнительных деталей/контекста... Из более раннего ядра Linux (2.6.28) здесь приведен пример смещения при использовании в системном вызове... он копирует смещение из пользовательского пространства во временную переменную, прежде чем в механизм вызова драйвера ядра, а затем копирует его обратно в файл пользователя. Это то, как смещение, которое видит драйвер, отключено от пользовательского представления, и облегчает ситуации, когда его смещение равно NULL в системном вызове, поэтому SEGVIO не возникает.
SYSCALL_DEFINE4(sendfile64, int, out_fd, int, in_fd, loff_t __user *, offset, size_t, count)
{
loff_t pos;
ssize_t ret;
if (offset) {
if (unlikely(copy_from_user(&pos, offset, sizeof(loff_t))))
return -EFAULT;
ret = do_sendfile(out_fd, in_fd, &pos, count, 0);
if (unlikely(put_user(pos, offset)))
return -EFAULT;
return ret;
}
return do_sendfile(out_fd, in_fd, NULL, count, 0);
}