Thursday, December 25, 2014

Procfs basics, Standard procfs files, Steps to create custom procfs file


In this post, I will cover basics of procfs, overview of standard procfs files and and steps to create custom proc entries.

procfs - Basics

procfs is a RAM based file system mounted at /proc. procfs is a communication mechanism between linux user space & kernel space. From user space, procfs files can be read to view information in kernel's internal data structures & written into to modify information in kernel's internal data structures. By using a RAM based file system, the well established file system interface is leveraged rather than create yet another specific interface.

Standard procfs files

  • Process specific entries: Linux kernel uses procfs to export (and also to receive user inputs) info about it's processes. Each process has a file named /proc/<pid>. Here is the list of files/sub-directories in process with pid 1:


root@babu-VirtualBox:/proc/1# ls
attr                          cpuset           limits             net                        sched           syscall
autogroup                cwd               loginuid          ns                         schedstat      task
auxv                        environ          map_files       oom_adj                sessionid      timers
cgroup                     exe                maps             oom_score             smaps          wchan
clear_refs                fd                  mem              oom_score_adj      stack
cmdline                   fdinfo            mountinfo      pagemap                stat
comm                      io                  mounts          personality             statm
coredump_filter      latency          mountstats     root                       status
root@babu-VirtualBox:/proc/1# 
I will not delve into the details of each file above. In some of my earlier posts, I displayed contents of some of the above files such as /proc/<pid>/smaps etc.
  • Kernel Data: Apart from process specific directories, /proc contains various other files offering insight into kernel's data structures:

root@babu-VirtualBox:/proc# ls | grep -v '^[0-9]' | column -x
acpi                             asound                       buddyinfo
bus                              cgroups                      cmdline
consoles                      cpuinfo                      crypto
devices                       diskstats                     dma
dri                               driver                         execdomains
fb                                filesystems                 fs
interrupts                    iomem                        ioports
irq                               kallsyms                     kcore
key-users                    kmsg                           kpagecount
kpageflags                  latency_stats               loadavg
locks                            lttng                            mdstat
meminfo                     misc                            modules
mounts                       mtrr                            net
pagetypeinfo              partitions                   sched_debug
schedstat                    scsi                              self
slabinfo                      softirqs                       stat
swaps                         sys                               sysrq-trigger
sysvipc                        timer_list                    timer_stats
tty                               uptime                        version
version_signature      vmallocinfo                vmstat
zoneinfo
root@babu-VirtualBox:/proc# 
I will not delve into the details of each file above. In some of my earlier posts, I displayed contents of some of the above files such as /proc/meminfo, /proc/slabinfo, /proc/buddyinfo etc.
  • Networking Info: /proc/net - TBD
  • IDE devices info : /proc/ide - TBD
  • SCSI devices info: /proc/scsi - TBD
  • Parallel port info: /proc/parport - TBD
  • tty info: /proc/tty - TBD
  • File System Info: /proc/fs - TBD
  • Console Info: /proc/consoles - TBD

Steps to create Custom procfs files

Kernel modules too can leverage procfs to display the information about their data structures as well as to get configuration info inputs from user.

procfs APIs have two versions - legacy version & latest version. The legacy version is deprecated in kernel versions >= 3.10. I will cover details of latest version in this post. The latest version is useful for kernel versions >= 3.13 .... 

  • How to create a new proc directory?
#define PROC_TEST_DIR "test"
        /*Create a new directory named PROC_TEST_DIR in /proc */
        proc_test_dir = proc_mkdir(PROC_TEST_DIR, NULL);
        if (!proc_test_dir)
        {
            printk("PROC_TEST_DIR creation failed.. Exiting. \r\n");
            return -1;
        }

  • How to create a new proc file? 

#define PROC_TEST_CONFIG_FILE "config"
        /* Create a new proc file in PROC_TEST_DIR in "rw- r-- r--" mode*/
        proc_test_config = proc_create(PROC_TEST_CONFIG_FILE, 0644,
                                proc_test_dir, &proc_test_config_file_ops);
        if (proc_test_dir == NULL) {
                printk("Could not create /proc/%s/%s\n", PROC_TEST_DIR,
                       PROC_TEST_CONFIG_FILE);
                remove_proc_entry(PROC_TEST_DIR, NULL);
                return -1;
        }

  • Reading APIs: Creating file alone is not sufficient. We need to write callback handlers that actually read/write into the module's data structures. Here is the code from the example code that displays it's data structures through procfs (reading from kernel module). This code uses seq_file interface. I will cover details of this interface in another blog post:


static void *proc_test_config_start(struct seq_file *, loff_t *);
static void *proc_test_config_next(struct seq_file *, void *, loff_t *);
static void proc_test_config_stop(struct seq_file *, void *);
static int proc_test_config_read(struct seq_file *, void *);
static struct seq_operations proc_test_config_seq_op = {
        .start =        proc_test_config_start,
        .next =         proc_test_config_next,
        .stop =         proc_test_config_stop,
        .show =         proc_test_config_read
};
static int proc_test_config_open(struct inode *inode, struct file *file)
{
        return seq_open(file, &proc_test_config_seq_op);
}
/**
 * This function is called at the beginning of a sequence.
 * ie, when:
 *      - the /proc file is read (first time)
 *      - after the function stop (end of sequence)
 *
 */
static void *proc_test_config_start(struct seq_file *m, loff_t * pos)
{
        static int cur_array_num=1, entry_num = 0;
        /* beginning a new sequence ? */
        if (*pos == 0) {
//printk("seq_start invoked at beg of first sequence \r\n");
                m->private = (void *)&cur_array_num;
                seq_printf(m, "\n\nConfig 1 Info:\n");
                seq_printf(m, "----------------------------\n");
                /* yes => return a non null value to begin the sequence */
                return &entry_num;
        } else if (*pos == 1) {
//printk("\n\nseq_start invoked at beg of second sequence \r\n");
                cur_array_num++;
                entry_num=0;
                m->private = (void *)&cur_array_num;
                seq_printf(m, "\n\nConfig 2 Info:\n");
                seq_printf(m, "----------------------------\n");
                /* yes => return a non null value to begin the sequence */
                return &entry_num;
        } else {
                /* no => it's the end of the sequence, return end to stop reading */
//printk("seq_start invoked at end of sequence \r\n");
                *pos = 0;
                cur_array_num=0;
                return NULL;
        }
}
/**
 * This function is called after the beginning of a sequence.
 * It's called untill the return is NULL (this ends the sequence).
 *
 */
static void *proc_test_config_next(struct seq_file *m, void *entry_num,
        loff_t * pos)
{
    int cur_array_num=*((int *)m->private);
    if (cur_array_num == 1)
    {
//printk("seq_next invoked in first sequence \r\n");
        *pos = 1;
        return NULL;            /* all data is displayed in one chunk */
    } else if (cur_array_num == 2) {
//printk("seq_next invoked in second sequence, entry num = %d \r\n", *((int *)entry_num));
        if (*((int *)entry_num) < MAX_CONFIG_ENTRIES2)
        {
            (*(int *)entry_num)++;
            return entry_num;
        } else
            return NULL;
    }
    return NULL;
}
static void proc_test_config_stop(struct seq_file *m, void *entry_num)
{
    if (*((int *)m->private) == 1)
    {
//printk("seq_stop invoked in first sequence \r\n");
        return;
    } else if (*((int *)m->private) == 2) {
//printk("seq_stop invoked in second sequence");
        seq_printf(m, "\n\n ");
        return;
    }
}
static int proc_test_config_read(struct seq_file *m, void *entry_num)
{
    int cur_array_num=*((int *)m->private);
//printk("seq_show invoked in sequence %d \r\n", cur_array_num);
    if (cur_array_num == 1)
    {
//printk("seq_show invoked in first sequence \r\n");
        seq_printf(m, "\n%s \n\n%s \n\n", proc_test_config_buff,
                proc_test_config_buff_format);
    } else if (cur_array_num == 2) {
//printk("seq_show invoked in second sequence \r\n");
        if (*((int*)entry_num) < MAX_CONFIG_ENTRIES2)
            seq_printf(m, "%d ", proc_test_config_buff2[*((int *)entry_num)]);
    }
    return 0;
}

  • Writing APIs: Here is some sample code that helps provide inputs to the kernel module through procfs (writing to kernel module). This code uses seq_file interface. I will cover details of this interface in another blog post.

static int proc_test_config_open(struct inode *, struct file *);
static ssize_t proc_test_config_write(struct file *,
        const char __user *, size_t , loff_t *);
static struct file_operations proc_test_config_file_ops = {
        .owner = THIS_MODULE,
        .open = proc_test_config_open,
        .read = seq_read,
        .write = proc_test_config_write,
        .llseek = seq_lseek,
        .release = seq_release,
};
static ssize_t proc_test_config_write(struct file *file,
        const char __user *buffer, size_t size, loff_t *ppos)
{
    char *buff, *buff1, **end_ptr = NULL;
    unsigned int i;
    if (size > CONFIG_BUFF_MAX_SIZE)
        return 0;
    copy_from_user(proc_test_config_buff_tmp, buffer, size);
    proc_test_config_buff_tmp[size] = '\0';
    buff = proc_test_config_buff_tmp;
    for (i=0; i< MAX_CONFIG_ENTRIES; i++)
    {
        buff1 = buff;
        while (isdigit(*buff))
                buff++;
        if (*buff != '\n') {
                if (*buff == ' ')
                        buff++;
                else
                        return 0;
        }
        switch (i) {
            case 1:
                use_hash_tbl_tmp = simple_strtoul(buff1, end_ptr, 10);
                break;
            case 2:
                max_hash_tbl_size_tmp = simple_strtoul(buff1, end_ptr, 10);
                break;
            case 3:
                log_stats_tmp = simple_strtoul(buff1, end_ptr, 10);
                break;
            case 4:
                max_stats_tmp = simple_strtoul(buff1, end_ptr, 10);
                break;
        }
    }
    *buff = '\0'; /* Ignoring any additional input user may have provided */
    /* Copy to the real config buffer & variables */
    strcpy(proc_test_config_buff, proc_test_config_buff_tmp);
    use_hash_tbl = use_hash_tbl_tmp;
    max_hash_tbl_size = max_hash_tbl_size_tmp;
    log_stats = log_stats_tmp;
    max_stats = max_stats_tmp;
    return size;
}

  • How to remove a proc file?
remove_proc_entry(PROC_TEST_CONFIG, proc_test_dir);

  • How to remove a proc directory?
remove_proc_entry(PROC_TEST_DIR, NULL);


Example kernel module illustrating procfs file creation:
https://github.com/babuneelam/procfsv2_seq


procfs read/write outputs for the example kernel module:
root@babu-VirtualBox:~/examples/proc/procv2_seq# ls
err           modules.order     proc_test.c    proc_test.mod.c        proc_test.mod.o
Makefile  Module.symvers  proc_test.ko  proc_test.mod.gcno  proc_test.o
root@babu-VirtualBox:~/examples/proc/procv2_seq#
root@babu-VirtualBox:~/examples/proc/procv2_seq# ls /proc | grep test
root@babu-VirtualBox:~/examples/proc/procv2_seq# insmod proc_test.ko
root@babu-VirtualBox:~/examples/proc/procv2_seq# ls /proc | grep test
test
root@babu-VirtualBox:~/examples/proc/procv2_seq# cd /proc/test
root@babu-VirtualBox:/proc/test# ls
config
root@babu-VirtualBox:/proc/test# cat config
Config 1 Info:
----------------------------
0 0 0 0 
Format:
          whether data sould be stored in a hash table
           Max Hash table size
           whether to log statistics
          Max number of statistics to be stored
   
Config 2 Info:
----------------------------
20 21 22 23 24 25
root@babu-VirtualBox:/proc/test# 
root@babu-VirtualBox:/proc/test# echo "1 0 1 0" > config
root@babu-VirtualBox:/proc/test# cat config
Config 1 Info:
----------------------------
1 0 1 0

Format:
          whether data sould be stored in a hash table
           Max Hash table size
           whether to log statistics
          Max number of statistics to be stored 
Config 2 Info:
----------------------------
20 21 22 23 24 25
root@babu-VirtualBox:/proc/test#
UA-48797665-1