web-dev-qa-db-fra.com

Comprendre loff_t * offp pour file_operations

Je conçois un pilote de périphérique qui lit et écrit simplement dans un tampon de caractères. Ma question concerne toutefois les deux fonctions de la structure file_operationsread et write. Je ne comprends pas vraiment ce que loff_t *offp est réellement. Je sais que pour les opérations de lecture et d'écriture, *offp est le décalage de fichier, qui signifie la position de lecture/écriture actuelle du fichier, mais je ne suis même pas sûr de ce que cela signifie d'écrire ou de lire dans/à partir d'un fichier de périphérique.

D'après ce que j'ai compris, et c'est comme ça que j'écris et que je lis à partir de mon appareil, c'est que je crée une structure qui représente mon appareil et que j'appelle my_char_struct qui est montrée ci-dessous.

struct my_char_structure{
    struct cdev my_cdev;
    struct semaphore sem;
    char *data;
    ssize_t data_size;
    unsigned int access_key;
    unsigned long size;
};

Il s'agit d'une structure statique qui est initialisée et pointée lorsque mon pilote est insmod en tant que tel. 

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);

Lorsque mon périphérique est ouvert, je crée simplement un pointeur sur le fichier ouvert pour pointer sur la structure statique que j'ai configurée pendant module_init(start_mod) en tant que telle ...

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;
}

Ce que mes méthodes de lecture et d'écriture font, c'est de modifier la structure initiale Dev, que j'ai indiquée avec mes fichiers ouverts. Quel que soit ce que je copy_to_user de ma structure correspond à ce que l'utilisateur considère avoir été écrit sur le périphérique et quel que soit le copy_from_user, l'utilisateur pense qu'il est en train d'écrire. Mais au-delà de la modification de ma structure initiale Dev, l'idée de position ou de décalage de fichier n'a de sens que si elle fait référence à un pointeur sur la mémoire mise en mémoire tampon dans le noyau pour une structure ou un type quelconque. C'est la seule interprétation que j'ai pour le décalage de fichier ... est-ce correct? Est-ce que c'est ce que le loff_t *offp fait référence ici?

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)

(étant donné que je comprends bien) Lorsque certaines opérations de fichier telles que lecture/écriture sont appelées et que je n'ai pas défini *offp personnellement, à quoi loff_t * offp a-t-il initialement été défini? 

Si dans la dernière opération file_operation offp = adresse_arbitraire (parce que je l’ai dit), est-ce à quoi l’offp serait défini lorsque cette opération est appelée à nouveau? 

Que se passe-t-il si d'autres opérations file_opens sont en cours d'exécution, est-il réglé sur ce que la dernière opération_fichier a laissée, ou conservera-t-il un onglet de l'opération file_open qu'il a utilisée et remplacera * offp par ce que l'utilitaire file_open avait? 

Le concept d'un périphérique char est trop abstrait pour moi quand il semble que le périphérique lui-même ne stocke pas les informations comme un fichier, mais plutôt le pilote qui enregistre les informations. J'espère avoir expliqué mon trouble et je vais éclaircir tout ce qui me semble ambigu.

18
Dr.Knowitall

"loff_t" est un "long offset", c’est-à-dire une position de recherche qui unifie la diversité insensée de off_t, off64_t, etc., de sorte que les conducteurs ne peuvent utiliser que loff_t sans s’inquiéter.

Le pointeur lui-même, au moment où vous entrez dans le pilote, pointe sur le décalage fourni par l'utilisateur (en supposant que son code utilisateur accède au pilote - techniquement, le noyau peut fournir le sien, mais le cas de l'utilisateur est celui à prendre en compte) via lseek ou llseek ou lseek64, etc., puis par des opérations de lecture et d'écriture ordinaires. Prenons le cas d’un fichier normal sur le disque: lorsque vous open le premier fichier, vous (en tant qu’utilisateur) demandez au noyau de fournir une structure de données qui garde la trace de votre position actuelle dans le fichier, de sorte que si vous read ou write certains octets, la prochaine read ou write reprend à l'endroit où vous l'avez laissée.

De plus, si vous dup le descripteur de fichier, ou effectuez l'équivalent de (par exemple) fork et exec en termes d'exécution d'une séquence de commandes, cette position de recherche est partagée par tous les processus hérités. Par conséquent, à l'invite du shell, la commande:

(prog1; prog2; prog3) > outputfile

crée un fichier de sortie, puis dups le descripteur des trois programmes, de sorte que la sortie écrite par prog2 soit insérée dans le fichier immédiatement après la sortie de prog1 et que la sortie de prog3 suit les deux autres, car ces trois processus distincts partagent le même sous-jacent. structure de données du noyau avec le même loff_t interne.

La même chose s'applique aux fichiers de pilote de périphérique. Lorsque vos fonctions de lecture et d’écriture sont appelées, vous recevez le "décalage actuel" tel que fourni par l’utilisateur, et vous pouvez (et devriez) le mettre à jour si nécessaire ... en supposant que cela soit nécessaire (par exemple, vous souhaitez fournir aux utilisateurs l’apparence d’un fichier normal, y compris le fait que les décalages de recherche se déplacent au fur et à mesure que vous lisez et écrivez). Si le périphérique a une application logique du décalage de recherche, vous pouvez l'utiliser ici.

Bien sûr, il y a beaucoup plus de choses sur les pilotes de périphériques, c'est pourquoi il y a des chapitres de livre entiers sur ce sujet (q.v.). :-)

20
torek

La réponse de Torek est excellente. En ajoutant juste un peu plus de détails/contexte ... Voici un exemple de décalage utilisé dans un appel système d'un noyau Linux antérieur (2.6.28) ... il copie le décalage de l'espace utilisateur dans une variable temporaire avant de commencer. dans le mécanisme d’invocation du pilote du noyau, puis le recopie dans le fichier utilisateur. C’est ainsi que le décalage que le pilote voit est découplé de sa vue utilisateur et facilite les situations dans lesquelles il est décalé à la valeur NULL dans l’appel système, de sorte qu’aucun SEGVIO ne se produise.

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);
}
0
clearlight