Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refine doc & code #158

Closed
minghu6 opened this issue Aug 27, 2022 · 7 comments
Closed

Refine doc & code #158

minghu6 opened this issue Aug 27, 2022 · 7 comments
Assignees

Comments

@minghu6
Copy link

minghu6 commented Aug 27, 2022

  1. Move the confusing note about struct proc_ops replacing with struct file_operations on linux >=5.6:

    lkmpg/lkmpg.tex

    Lines 957 to 990 in 637e707

    Since Linux v5.6, the \cpp|proc_ops| structure was introduced to replace the use of the \cpp|file_operations| structure when registering proc handlers.
    \subsection{The file structure}
    \label{sec:file_struct}
    Each device is represented in the kernel by a file structure, which is defined in \src{include/linux/fs.h}.
    Be aware that a file is a kernel level structure and never appears in a user space program.
    It is not the same thing as a \cpp|FILE|, which is defined by glibc and would never appear in a kernel space function.
    Also, its name is a bit misleading; it represents an abstract open `file', not a file on a disk, which is represented by a structure named \cpp|inode|.
    An instance of struct file is commonly named \cpp|filp|.
    You'll also see it referred to as a struct file object.
    Resist the temptation.
    Go ahead and look at the definition of file.
    Most of the entries you see, like struct dentry are not used by device drivers, and you can ignore them.
    This is because drivers do not fill file directly; they only use structures contained in file which are created elsewhere.
    \subsection{Registering A Device}
    \label{sec:register_device}
    As discussed earlier, char devices are accessed through device files, usually located in \verb|/dev|.
    This is by convention. When writing a driver, it is OK to put the device file in your current directory.
    Just make sure you place it in \verb|/dev| for a production driver.
    The major number tells you which driver handles which device file.
    The minor number is used only by the driver itself to differentiate which device it is operating on, just in case the driver handles more than one device.
    Adding a driver to your system means registering it with the kernel.
    This is synonymous with assigning it a major number during the module's initialization.
    You do this by using the \cpp|register_chrdev| function, defined by \src{include/linux/fs.h}.
    \begin{code}
    int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
    \end{code}

    The struct proc_ops would replace the struct file-operations only when create proc releated defined on <linux/proc_fs.h>
    In other word, regist device doesn't be affected, still using struct file_operations. It's confusing write this note above the regist device code (FOE ME, I HAVE SPENT PLENTY OF TIME ON SEARCHING HOW TO REGIST DEVICE USING STRUCT PROC_OPS)
    I highly recommend to move this note down to next code example which replacing takes place truely!

  2. lkmpg/examples/chardev.c

    Lines 25 to 39 in 637e707

    #define BUF_LEN 80 /* Max length of the message from the device */
    /* Global variables are declared as static, so are global within the file. */
    static int major; /* major number assigned to our device driver */
    enum {
    CDEV_NOT_USED = 0,
    CDEV_EXCLUSIVE_OPEN = 1,
    };
    /* Is device open? Used to prevent multiple access to device */
    static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED);
    static char msg[BUF_LEN]; /* The msg the device will give when asked */

    The msg char array should be declared with length of BUF_LEN + 1 since BUF_LEN is max length of message.

  3. return -EINVAL;

    On device_write, It's prefered to use EOPNOTSUPP instead of EINVAL since the former raises unsupported error, the latter raises invalid argument.

  4. Improve read/write fucntion code among fsproc{1-3}.c:

    lkmpg/examples/procfs3.c

    Lines 23 to 40 in 637e707

    static ssize_t procfs_read(struct file *filp, char __user *buffer,
    size_t length, loff_t *offset)
    {
    static int finished = 0;
    if (finished) {
    pr_debug("procfs_read: END\n");
    finished = 0;
    return 0;
    }
    finished = 1;
    if (copy_to_user(buffer, procfs_buffer, procfs_buffer_size))
    return -EFAULT;
    pr_debug("procfs_read: read %lu bytes\n", procfs_buffer_size);
    return procfs_buffer_size;
    }

    lkmpg/examples/procfs3.c

    Lines 41 to 53 in 637e707

    static ssize_t procfs_write(struct file *file, const char __user *buffer,
    size_t len, loff_t *off)
    {
    if (len > PROCFS_MAX_SIZE)
    procfs_buffer_size = PROCFS_MAX_SIZE;
    else
    procfs_buffer_size = len;
    if (copy_from_user(procfs_buffer, buffer, procfs_buffer_size))
    return -EFAULT;
    pr_debug("procfs_write: write %lu bytes\n", procfs_buffer_size);
    return procfs_buffer_size;
    }

These code are a little wired and far from decent, the parameter loff_t * is ignored incorrectly and so the behavior is quite different from normal read/write.I've reimplemented read/write code, here is full code:

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/version.h>
// #include <linux/slab.h>  // kmalloc etc.

/* Header */
#define do_sslice(news, s, n) for(int i=0;i<n;++i) { news[i]=s[i]; };news[n]='\0';

#define proc_name "hello"
#define PROC_BUF_CAP (size_t)1024

static ssize_t proc_read(struct file *filp, char __user *buffer, size_t size,
                         loff_t *offset);
static ssize_t proc_write(struct file *filp, const char __user *buffer,
                          size_t size, loff_t *offset);
static int proc_open(struct inode *, struct file*);
static int proc_close(struct inode *, struct file*);


/* Source */

#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 6, 0)
/// @brief Since Linux 5.6, or else use file_operations on <linux/fs.h> instead
/// for older version
static struct proc_ops proc_ops = {
    .proc_read = proc_read,
    .proc_write = proc_write,
    .proc_open = proc_open,
    .proc_release = proc_close,
};
#else
#include <linux/fs.h>
static struct file_operations proc_ops = {
    .read = proc_read,
    .write = proc_write,
};
#endif

static struct proc_dir_entry *proc_file;
static char proc_buf[PROC_BUF_CAP + 1];
static char helper_buf[PROC_BUF_CAP + 1];


static ssize_t proc_read(struct file *filp, char __user *buffer, size_t size,
                         loff_t *offset) {

    static char *s = "ALOHO!";
    size_t slen = strlen(s) + 1;

    size_t actual_read;

    if (*offset >= slen) {
        pr_info("copy to user finished.");
        return 0;
    }

    if (copy_to_user(buffer, s+*offset,
                     (actual_read = min(size, (size_t)(slen - *offset))))) {
        pr_err("copy to user failed!");
        return -EFAULT;
    }

    pr_info("read /proc/%s\n", filp->f_path.dentry->d_name.name);
    *offset += actual_read;

    return actual_read;
}


static ssize_t proc_write(struct file *filp, const char __user *buffer,
                          size_t size, loff_t *offset) {
    size_t actual_write = min(size, PROC_BUF_CAP-(size_t)*offset);
    static char slice[PROC_BUF_CAP];

    if (filp->f_flags & O_APPEND) {
        return -EOPNOTSUPP;
    }

    if (*offset >= PROC_BUF_CAP) {
        pr_info("write to end.");
        return 0;
    }

    if (copy_from_user(proc_buf + *offset, buffer, actual_write)) {
        pr_err("copy from user failed!");
        return -EFAULT;
    }

    *offset += actual_write;

    do_sslice(helper_buf, proc_buf, *offset);
    pr_info("write to %s\n", helper_buf);

    return actual_write;
}


static int proc_open(struct inode *inode, struct file* filp) {
    try_module_get(THIS_MODULE);
    return 0;
}


static int proc_close(struct inode *inode, struct file* filp) {
    module_put(THIS_MODULE);
    return 0;
}


static int __init proc_init(void) {
    if (!(proc_file = proc_create(proc_name, 0644, NULL, &proc_ops))) {
        proc_remove(proc_file);
        pr_alert("Error: couldn't init /proc/%s\n", proc_name);
        return -ENOMEM;
    }

    pr_info("/proc/%s\n", proc_name);
    return 0;
}

static void __exit proc_exit(void) {
    proc_remove(proc_file);
    pr_info("/proc/%s removed\n", proc_name);
}

module_init(proc_init);
module_exit(proc_exit);

MODULE_LICENSE("GPL");

It need more modern c standard config than C90, for example ccflags-y += -std=gnu17.

@minghu6
Copy link
Author

minghu6 commented Sep 2, 2022

  1. Unmached code and comment
    /* ttys were originally hardware devices, which (usually) strictly
    * followed the ASCII standard. In ASCII, to move to a new line you
    * need two characters, a carriage return and a line feed. On Unix,
    * the ASCII line feed is used for both purposes - so we can not
    * just use \n, because it would not have a carriage return and the
    * next line will start at the column right after the line feed.
    *
    * This is why text files are different between Unix and MS Windows.
    * In CP/M and derivatives, like MS-DOS and MS Windows, the ASCII
    * standard was strictly adhered to, and therefore a newline requires
    * both a LF and a CR.
    */
    (ttyops->write)(my_tty, "\015\012", 2);

    "\015\012" is \v\b rather than CRLF \r\n.

@linD026
Copy link
Collaborator

linD026 commented Sep 7, 2022

Hi @minghu6 ,
Sorry for the late to reply.

  1. Move the confusing note about struct proc_ops replacing with struct file_operations on linux >=5.6:

    lkmpg/lkmpg.tex

    Lines 957 to 990 in 637e707

    Since Linux v5.6, the \cpp|proc_ops| structure was introduced to replace the use of the \cpp|file_operations| structure when registering proc handlers.
    \subsection{The file structure}
    \label{sec:file_struct}
    Each device is represented in the kernel by a file structure, which is defined in \src{include/linux/fs.h}.
    Be aware that a file is a kernel level structure and never appears in a user space program.
    It is not the same thing as a \cpp|FILE|, which is defined by glibc and would never appear in a kernel space function.
    Also, its name is a bit misleading; it represents an abstract open `file', not a file on a disk, which is represented by a structure named \cpp|inode|.
    An instance of struct file is commonly named \cpp|filp|.
    You'll also see it referred to as a struct file object.
    Resist the temptation.
    Go ahead and look at the definition of file.
    Most of the entries you see, like struct dentry are not used by device drivers, and you can ignore them.
    This is because drivers do not fill file directly; they only use structures contained in file which are created elsewhere.
    \subsection{Registering A Device}
    \label{sec:register_device}
    As discussed earlier, char devices are accessed through device files, usually located in \verb|/dev|.
    This is by convention. When writing a driver, it is OK to put the device file in your current directory.
    Just make sure you place it in \verb|/dev| for a production driver.
    The major number tells you which driver handles which device file.
    The minor number is used only by the driver itself to differentiate which device it is operating on, just in case the driver handles more than one device.
    Adding a driver to your system means registering it with the kernel.
    This is synonymous with assigning it a major number during the module's initialization.
    You do this by using the \cpp|register_chrdev| function, defined by \src{include/linux/fs.h}.
    \begin{code}
    int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
    \end{code}

    The struct proc_ops would replace the struct file-operations only when create proc releated defined on <linux/proc_fs.h>
    In other word, regist device doesn't be affected, still using struct file_operations. It's confusing write this note above the regist device code (FOE ME, I HAVE SPENT PLENTY OF TIME ON SEARCHING HOW TO REGIST DEVICE USING STRUCT PROC_OPS)
    I highly recommend to move this note down to next code example which replacing takes place truely!

Yes, it won't affect the registering of the device, but the paragraph here actually talks about the file operation. And, it seems like the file operation will be related to the proc_ops. So, I think the description of proc_ops replacing the file_ops is still needed in the section here.
But still, it might confuse others. So, I will add more sentences to hint that this description talks about the file_ops and proc_ops rather than registering the device.

  1. lkmpg/examples/chardev.c

    Lines 25 to 39 in 637e707

    #define BUF_LEN 80 /* Max length of the message from the device */
    /* Global variables are declared as static, so are global within the file. */
    static int major; /* major number assigned to our device driver */
    enum {
    CDEV_NOT_USED = 0,
    CDEV_EXCLUSIVE_OPEN = 1,
    };
    /* Is device open? Used to prevent multiple access to device */
    static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED);
    static char msg[BUF_LEN]; /* The msg the device will give when asked */

    The msg char array should be declared with length of BUF_LEN + 1 since BUF_LEN is max length of message.

The macro, BUF_LEN, is the length of the buffer, not the message length.
So, I think there doesn't need to change.

  1. return -EINVAL;

    On device_write, It's prefered to use EOPNOTSUPP instead of EINVAL since the former raises unsupported error, the latter raises invalid argument.

Yes, it should uses the EOPNOTSUPP.
Thanks.

  1. Improve read/write fucntion code among fsproc{1-3}.c:

    lkmpg/examples/procfs3.c

    Lines 23 to 40 in 637e707

    static ssize_t procfs_read(struct file *filp, char __user *buffer,
    size_t length, loff_t *offset)
    {
    static int finished = 0;
    if (finished) {
    pr_debug("procfs_read: END\n");
    finished = 0;
    return 0;
    }
    finished = 1;
    if (copy_to_user(buffer, procfs_buffer, procfs_buffer_size))
    return -EFAULT;
    pr_debug("procfs_read: read %lu bytes\n", procfs_buffer_size);
    return procfs_buffer_size;
    }

    lkmpg/examples/procfs3.c

    Lines 41 to 53 in 637e707

    static ssize_t procfs_write(struct file *file, const char __user *buffer,
    size_t len, loff_t *off)
    {
    if (len > PROCFS_MAX_SIZE)
    procfs_buffer_size = PROCFS_MAX_SIZE;
    else
    procfs_buffer_size = len;
    if (copy_from_user(procfs_buffer, buffer, procfs_buffer_size))
    return -EFAULT;
    pr_debug("procfs_write: write %lu bytes\n", procfs_buffer_size);
    return procfs_buffer_size;
    }

These code are a little wired and far from decent, the parameter loff_t * is ignored incorrectly and so the behavior is quite different from normal read/write.I've reimplemented read/write code, here is full code:

It need more modern c standard config than C90, for example ccflags-y += -std=gnu17.

The read/write functions are a little weird. I will modify them.
For using modern C standards, we need to consider more.
Since kernel version v5.18, especially after this commit "Kbuild: move to -std=gnu11", kernel supports C11 features. We have the option to move to it.
But considering LKMPG is supporting the v5.x version, we must stick to the C89 still a while.
However, for C17, it might be the wrong standard to use.

@linD026
Copy link
Collaborator

linD026 commented Sep 7, 2022

  1. Unmached code and comment

    /* ttys were originally hardware devices, which (usually) strictly
    * followed the ASCII standard. In ASCII, to move to a new line you
    * need two characters, a carriage return and a line feed. On Unix,
    * the ASCII line feed is used for both purposes - so we can not
    * just use \n, because it would not have a carriage return and the
    * next line will start at the column right after the line feed.
    *
    * This is why text files are different between Unix and MS Windows.
    * In CP/M and derivatives, like MS-DOS and MS Windows, the ASCII
    * standard was strictly adhered to, and therefore a newline requires
    * both a LF and a CR.
    */
    (ttyops->write)(my_tty, "\015\012", 2);

    "\015\012" is \v\b rather than CRLF \r\n.

The "\015\012" seems correct.
According to the ASCII table, CR is 015 and LF is 012.
And, \v\b is \013\010.
I might be wrong, please provide more information about this.

Thanks

linD026 added a commit to linD026/lkmpg that referenced this issue Sep 7, 2022
According to the issue sysprog21#158, it is more appropriate to use the value.
linD026 added a commit to linD026/lkmpg that referenced this issue Sep 7, 2022
According to the issue sysprog21#158, it is more appropriate to use the value
EOPNOTSUPP rather than EINVAL.
@linD026
Copy link
Collaborator

linD026 commented Sep 7, 2022

  1. return -EINVAL;

    On device_write, It's prefered to use EOPNOTSUPP instead of EINVAL since the former raises unsupported error, the latter raises invalid argument.

Yes, it should uses the EOPNOTSUPP. Thanks.

Sorry, I recheck the write man page to make sure that the change here is suitable to the definition.
In the man page, it doesn't have any error value that is EOPNOTSUPP, ENOTSUP, or even ENOTSUPP (kernel defined).
And from the description of EINVAL in the manual:

EINVAL fd is attached to an object which is unsuitable for
       writing; or the file was opened with the O_DIRECT flag,
       and either the address specified in buf, the value
       specified in count, or the file offset is not suitably
       aligned.

fd is attached to an object which is unsuitable for writing

The EINVAL is correct. We don't have to change to EOPNOTSUPP.

@linD026
Copy link
Collaborator

linD026 commented Sep 7, 2022

  1. lkmpg/examples/chardev.c

    Lines 25 to 39 in 637e707

    #define BUF_LEN 80 /* Max length of the message from the device */
    /* Global variables are declared as static, so are global within the file. */
    static int major; /* major number assigned to our device driver */
    enum {
    CDEV_NOT_USED = 0,
    CDEV_EXCLUSIVE_OPEN = 1,
    };
    /* Is device open? Used to prevent multiple access to device */
    static atomic_t already_open = ATOMIC_INIT(CDEV_NOT_USED);
    static char msg[BUF_LEN]; /* The msg the device will give when asked */

    The msg char array should be declared with length of BUF_LEN + 1 since BUF_LEN is max length of message.

The macro, BUF_LEN, is the length of the buffer, not the message length. So, I think there doesn't need to change.

Oops, I saw the comments.
Sorry again.

@jserv
Copy link
Contributor

jserv commented Sep 8, 2022

Ready to close?

@linD026
Copy link
Collaborator

linD026 commented Sep 8, 2022

Sure, I think.
If there are any other opinions we can just reopen it.

@linD026 linD026 closed this as completed Sep 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants