LinuxQuestions.org
Latest LQ Deal: Latest LQ Deals
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Non-*NIX Forums > Programming
User Name
Password
Programming This forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.

Notices


Reply
  Search this Thread
Old 08-03-2014, 11:50 PM   #1
zerop
Member
 
Registered: Jul 2014
Posts: 65

Rep: Reputation: Disabled
Question mmap example problem


http://people.ee.ethz.ch/~arkeller/l...wto.html#ss5.2

after insmod the mmap kernel module example,
and then run mmap user space program example.

no output string seen in initial message and changed message

then i compile the use space program again, got error tell me to submit
error report

wonder@wonder-VirtualBox:~/qsort$ sudo gcc -o main main.c
main.c:1:0: internal compiler error: Segmentation fault
Please submit a full bug report,
with preprocessed source if appropriate.
See <file:///usr/share/doc/gcc-4.6/README.Bugs> for instructions.
Preprocessed source stored into /tmp/ccIXW9fm.out file, please attach this to your bugreport.

Code:
	int configfd;
	configfd = open("/sys/kernel/debug/mmap_example", O_RDWR);
	if(configfd < 0) {
		perror("open");
		return -1;
	}

	char * address = NULL;
	//address = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, configfd, 0);
	printf("page size=%d\n", getpagesize());
	address = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, configfd, 0);

	if (address == MAP_FAILED) {
		perror("mmap");
		return -1;
	}

	printf("initial message: %s\n", address);
	memcpy(address + 11 , "*user*", 6);
	printf("changed message: %s\n", address);
	close(configfd);

Last edited by zerop; 08-03-2014 at 11:53 PM.
 
Old 08-05-2014, 08:24 AM   #2
a4z
Senior Member
 
Registered: Feb 2009
Posts: 1,727

Rep: Reputation: 742Reputation: 742Reputation: 742Reputation: 742Reputation: 742Reputation: 742Reputation: 742
you did not provide your compiler version
you did not provide you platform
your code listing is incomplete
this makes it hard to help

I downloaded this

http://people.ee.ethz.ch/~arkeller/l...de/mmap_user.c
and it compiles
(slackware 14.1, gcc 8,2)

you do not need sudo for compiling

possible you have copied the text from the web and some invisable invalid characters in your source?
 
Old 08-05-2014, 09:10 AM   #3
zerop
Member
 
Registered: Jul 2014
Posts: 65

Original Poster
Rep: Reputation: Disabled
Ubuntu

uname -r
3.8.0-29-generic

gcc (Ubuntu/Linaro 4.6.3-1ubuntu5) 4.6.3
Copyright (C) 2011 Free Software Foundation, Inc

user space
Code:
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
// use uint_least32_t
#include <inttypes.h>
#include <netinet/in.h>
// Error handling
#include <setjmp.h>
// Communication between kernel module and user space
#include <signal.h>
// GTKmm
/*
#include <memory.h>
#include <gtkmm.h>
#include <libgdamm.h>
#include <libgnomedbmm.h>*/
// Install
// sudo apt-get install libgtkmm-3.0-dev
// sudo apt-get install libgdamm-5.0-13
// Compile
// g++ main.c -o main `pkg-config gtkmm-3.0 libgdamm-5.0 --cflags --libs`
// mmap
#include <fcntl.h>
#include <sys/mman.h>
// syscall
#include <sys/syscall.h>
#define MYSYSCALL 325

int v[99];
icmp(uint_least32_t *a) {a=*a-*1[&a];}

scmp2(char** a)
{
  return (a[0]-a[1])==0?1:0;//equal = 1, not equal = 0
}
//int f(int* a, int* b)
//{
  //return *a-*b;
//}
uint_least32_t n;
uint_least32_t k;
uint_least32_t i;
char *strcat2(char *dest2, char *src)
{
	uint_least32_t dest_len = strlen(dest2);
        uint_least32_t src_len = strlen(src);
	uint_least32_t i;
	char* dest = (char*)malloc(dest_len+src_len);
	for(i=src_len-1; i>=0; --i)
        {
        //for(i=(src_len-1) && src[i] != '\0'; i>=0; --i)
            if(src[i] != '\0')
		dest[dest_len + i] = src[i];
        }
	for(i=dest_len-1; i>=0; --i)
        {
        //for(i=(dest_len-1) && dest2[i] != '\0'; i>=0; --i)
            if(dest2[i] != '\0')
		dest[i] = dest2[i];
        }
	dest[dest_len+i] = '\0';
	return dest;
}
void signal_handler_func(int sig)
{

//handle the action corresponding to the signal here
}
static jmp_buf s_jumpBuffer;

void signal_handler (int signum){
  if (signum == SIGIO) printf ("SIGIO\r\n"); return;
}
/*
class ExampleWindow : public Gtk::Window
{
public:
  ExampleWindow(const Glib::RefPtr<Gnome::Gda::Dict>& dict);
    
private:
#ifdef GLIBMM_EXCEPTIONS_ENABLED
  void create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict);
#else
  void create_model(const Glib::RefPtr<Gnome::Gda::Dict>& dict, std::auto_ptr<Glib::Error>& error);
#endif
  Glib::RefPtr<Gnome::Gda::DataModelQuery> m_model;
    
  Gtk::VBox m_box;
  Gtk::Label m_label;
  Gnome::Db::Grid* m_grid;
};*/
main()
{
	int configfd;
	configfd = open("/sys/kernel/debug/mmap_example", O_RDWR);
	if(configfd < 0) {
		perror("open");
		return -1;
	}

	char * address = NULL;
	//address = mmap(NULL, PAGE_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED, configfd, 0);
	printf("page size=%d\n", getpagesize());
	address = mmap(NULL, getpagesize(), PROT_READ|PROT_WRITE, MAP_SHARED, configfd, 0);

	if (address == MAP_FAILED) {
		perror("mmap");
		return -1;
	}

	printf("initial message: %s\n", address);
	memcpy(address + 11 , "*user*", 6);
	printf("changed message: %s\n", address);
	close(configfd);	

/*
   char *buf [20];
   memcpy(buf, "hi kernel", strlen("hi kernel") +1);
   syscall(MYSYSCALL, buf, 10);
   printf("kernel said %s\n", buf);
   int i=1 ;
   signal(SIGIO, signal_handler);
   printf("My PID is %d.\n",getpid());
   while (i);
*/
/*
   char* order[1500];
   memcpy(&order[0], "TM", 2);
   printf("set TM\n");
   memcpy(&order[2], "HK", 2);
   memcpy(&order[4], "BU", 2);
   memcpy(&order[6], "0005.HK", 7);
   printf("set 0005\n");

   char hello[20];
   char* countrycode = "HK\0";
   hello[0] = *countrycode;
   hello[3] = '\n';
   printf(hello);
   printf("after hello\n");
*/
  //Kernel to user space
  /*
  signal(SIGIO, &signal_handler_func); 
  fcntl(fd, F_SETOWN, getpid());
  oflags = fcntl(fd, F_GETFL);
  fcntl(fd, F_SETFL, oflags | FASYNC);
  */
/*
  unsigned char src_mac[6] = {0x10, 0x78, 0xd2, 0xad, 0x90, 0xcb};
  unsigned char dest_mac[6] = {0x10, 0x78, 0xd2, 0xad, 0x90, 0xcb};
  unsigned char *head = malloc(14);
  printf("vmalloc(14)");
  memcpy(&head[0], src_mac, 6);
  printf("src_mac\n");
  memcpy(&head[6], dest_mac, 6);
  printf("dest_mac\n");
  unsigned char type[2] = {0x08, 0x00};
  //if(setjmp(s_jumpBuffer)){
     //printf("exception happened\n");
  //}else{
     //memcpy(&head[18], type, 4);
  //}
  printf("pass\n");
  //printf("%x\n", head);
  const char* ss[2];
  //ss[0] = malloc(20);
  ss[0]="martin";
  ss[1]="martin";
  //strcpy(ss[2], "G power\0");
  //g(ss);
  printf("%d\n", scmp2(ss));
  //printf("%s\n", strcat3(ss));
  char buffer[10];
  
  int temp;
  scanf("%d",&temp);
  //itoa(i,buffer,8);
  sprintf(buffer, "%d", temp);
  printf("string:%s\n",buffer);
  char* hello_str1 = "Step3 ";
  char* hello_str2 = "Step1 ";
  char* hello_str3 = strcat2(hello_str1, hello_str2);
  const char* sd[2];
  sd[0] = "Step3 ";
  sd[1] = "Step1 ";
  //strcat3(sd);
  printf("xmen:%s\n", sd[0]);
  free(hello_str3);
  //scanf("%d",&n);
  //for(gets(&k); ~scanf("%d", &k);)
  //for(; ~scanf("%d", &k);)
     //if(i++) printf("%d\n", k);
 */
/* 
  scanf("%d",&n);
  printf("hello\n");
  //for(;~scanf("%d",s- --n);)
     //printf("%d\n", s[i]);
  for(i=n;i>=1;--i)
  {
    printf("hello:%d\n", n-i);
    scanf("%d",&v[n-i]);
    printf("%d\n", v[n-i]);
  }
  printf("hello 2\n");
  qsort(v,n,4,icmp);
  for(i=n; i>=1; --i)
    printf("%d\n",v[n-i]);
*/
  //for(;*--p;)
    //printf("%d\n",*p);
    //
  
}
module
Code:
#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif
//#include <string.h>
//#include <stdio.h>
//#include <bits/setjmp.h>
// system call by user space
#include <linux/linkage.h>
// read and write pro/file
#include <linux/proc_fs.h>
#include <linux/workqueue.h>
#include <linux/sched.h>
#include <linux/delay.h> 
#include <linux/vmalloc.h>
#include <linux/timex.h>
#include <asm/msr.h>
#include <asm/uaccess.h>
//#include <sys/malloc.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/sched.h>
#include <linux/kernel.h> // printk() 
#include <linux/slab.h> // kmalloc() 
#include <linux/errno.h>  // error codes 
#include <linux/types.h>  // size_t 
#include <linux/interrupt.h> // mark_bh 

#include <linux/in.h>
#include <linux/netdevice.h>   // struct device, and other headers 
#include <linux/etherdevice.h> // eth_type_trans 
#include <linux/ip.h>          // struct iphdr 
#include <linux/tcp.h>         // struct tcphdr 
#include <linux/skbuff.h>
//nproc
//lscpu
#include <linux/mpi.h>
#include "snull.h"


//readelf -S ./hello.ko
//[ 2] .text             PROGBITS        00000000 000060 0008ac 00   
//[21] .data             PROGBITS        00000000 001500 00000c 00  
//[25] .bss              NOBITS          00000000 0016a0 00008c 00
//add-symbol-file ./hello.ko 0xd0000060 \
//-s .bss 0xd00017c0 \
//-s .data 0xd0001620
//p snull_devs[0]
#include <linux/in6.h>
#include <asm/checksum.h>
//wonder@wonder-VirtualBox:~/layer$ sudo make -C /usr/src/linux-headers-3.8.0-29-generic -C /usr/include/i386-linux-gnu/sys -C /usr/include/i386-linux-gnu hello.c M=/home/wonder/layer modules
MODULE_LICENSE("GPL");
#define MODULE_VERS "1.0"
#define MODULE_NAME "procfs_example"
#define FOOBAR_LEN 8

//wonder@wonder-VirtualBox:~/layer$ sudo gedit /usr/src/linux-headers-3.8.0-29/include/asm-generic/unistd.h
//wonder@wonder-VirtualBox:~/layer$ sudo gedit /usr/src/linux-headers-3.8.0-lude/linux/syscalls.h

//static jmp_buf s_jumpBuffer;
//#define TRY do{ jmp_buf ex_buf__; if( !setjmp(ex_buf__) ){
//#define CATCH } else {
//#define ETRY } }while(0)
//#define THROW longjmp(ex_buf__, 1)

#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/mm.h>  /* mmap related stuff */

typedef union
{ unsigned long long int64;
struct {unsigned int lo, hi;} int32;
} tsc_counter;
#define RDTSC(cpu_c) \
__asm__ __volatile__ ("rdtsc" : \
"=a" ((cpu_c).int32.lo), \
"=d"((cpu_c).int32.hi))

struct dentry  *file1;

struct mmap_info {
	char *data;	/* the data */
	int reference;       /* how many times it is mmapped */  	
};

struct fb_data_t {
	char name[FOOBAR_LEN + 1];
	char value[FOOBAR_LEN + 1];
};

static struct proc_dir_entry *example_dir, *bar_file, *symlink;
struct fb_data_t bar_data;
struct workqueue_struct *test_wq;
struct delayed_work test_dwq;

char *hello_str = "Hello, world!\n";
 
void delay_func(struct work_struct *work);

static struct tasklet_struct my_tasklet ;  
static void tasklet_handler(unsigned long data)
{

    //memcpy(skb_put(databuffer, 14), "data here", 14);
    //memcpy(&databuffer[14], "data1", 5);
/*
    printk(KERN_INFO "len=%d\n", sizeof(databuffer));
    databuffer->dev = snull_devs[0];
    printk(KERN_INFO "set up snull_devs 2");
    databuffer->data_len = ETH_DATA_LEN;
    printk(KERN_INFO "1500");
    databuffer->len = ETH_FRAME_LEN;
    unsigned char *data2 = vmalloc(1500);
    printk(KERN_INFO "vmalloc(1500)");
    memcpy(&data2, "martin5", 7);
    printk(KERN_INFO "memcpy martin5");
    databuffer->data = data2;
    printk(KERN_INFO "databuffer->data = data");
    unsigned char *head = vmalloc(14);
    printk(KERN_INFO "vmalloc(14)");
    memcpy(&head[0], src_mac, ETH_ALEN);
    printk(KERN_INFO "src_mac");
    memcpy(&head[6], dest_mac, ETH_ALEN);
    printk(KERN_INFO "dest_mac");
    memcpy(&head[12], type, 2);
    printk(KERN_INFO "type");
    databuffer->head = head;
    printk(KERN_INFO "head");
    //0-5
    //6-11
    //12-13
    //struct ethhdr *eth = (struct ethhdr *)skb_push(databuffer, ETH_HLEN);
    //databuffer->data -= ETH_HLEN;
    //databuffer->len += ETH_HLEN;
    //struct ethhdr *eth = (struct ethhdr *)databuffer->data;
    //eth->h_proto = htons(80);
    //memcpy(eth->h_source, src_mac, ETH_ALEN); 
    //memcpy(eth->h_dest, dest_mac, ETH_ALEN);
    //databuffer->mac_header =eth;
*/
    printk(KERN_INFO "before emit\n");
    //if(dev_queue_xmit(databuffer) != NET_XMIT_SUCCESS)
    //{
       //printk(KERN_INFO "not send");
    //}
    printk(KERN_INFO "after emit\n");
    printk(KERN_ALERT "3333tasklet_handler is running./n");
	//tasklet_schedule(&my_tasklet);
}
struct net_device *snull_devs[1];
/*
 * A structure representing an in-flight packet.
 */
struct snull_packet {
	struct snull_packet *next;
	struct net_device *dev;
	int	datalen;
	u8 data[ETH_DATA_LEN];
};  
void delay_func(struct work_struct *work)
{
    //int i;
 
    printk(KERN_INFO "My name is delay_func!\n");
    //int ret = queue_delayed_work(test_wq, &test_dwq, 0);
    //for (i = 0; i < 3; i++) {
        //printk(KERN_ERR "delay_fun:i=%d\n", i);
        //msleep(1000);
    //}
} 
static int
hello_read_proc(char *buffer, char **start, off_t offset, int size, int *eof, void *data)
{
        
        //char*hello_str = buffer;
        int len = strlen(hello_str); /* Don't include the null byte. */
        /*
         * We only support reading the whole string at once.
         */
        if(size < len)
           return -EINVAL;
        /*
         * If file position is non-zero, then assume the string has
         * been read and indicate there is no more data to be read.
         */
        if (offset != 0)
           return 0;
        /*
         * We know the buffer is big enough to hold the string.
         */
        strcpy(buffer, hello_str);
        /*
         * Signal EOF.
         */
        *eof = 1;

        return len;
}
//strcmp(char** a)
//{
  //return (a[0]-a[1])==0?1:0;//equal = 1, not equal = 0
//}
char *strcat2(char *dest2, char *src)
{
	int dest_len = strlen(dest2);
        int src_len = strlen(src);
	int i;
	char* dest = (char*)vmalloc(dest_len+src_len);
	for(i=0; i<src_len && src[i] != '\0'; i++)
		dest[dest_len + i] = src[i];
	for(i=0; i<dest_len && dest2[i] != '\0'; i++)
		dest[i] = dest2[i];
	dest[dest_len+i] = '\0';
	return dest;
}
#define crBegin static int state=0; switch(state) { case 0:
#define crReturn(i,x) do { state=i; return x; case i:; } while (0)
#define crFinish }
int function(void) {
    static int i;
    crBegin;
    for (i = 0; i < 10; i++)
        crReturn(1, i);
    crFinish;
}
//struct foo {
    //int a, b, c;
    //LIST_ENTRY(foo) pointers;
//};
//struct Torrent {
    //LIST_HEAD(foo_list, foo) bar;
//};

struct siginfo sinfo;
pid_t pid;
struct task_struct *task;

asmlinkage long sys_mysyscall(char __user *buf, int len)
{
  char msg [200];
  if(strlen_user(buf) > 200)
           return -EINVAL;
  copy_from_user(msg, buf, strlen_user(buf));
  printk("mysyscall: %s\n", msg);
  copy_to_user(buf, "hello world", strlen("hello world")+1);
  return strlen("hello world")+1;
}

/* keep track of how many times it is mmapped */

void mmap_open(struct vm_area_struct *vma)
{
	struct mmap_info *info = (struct mmap_info *)vma->vm_private_data;
	info->reference++;
}

void mmap_close(struct vm_area_struct *vma)
{
	struct mmap_info *info = (struct mmap_info *)vma->vm_private_data;
	info->reference--;
}

/* nopage is called the first time a memory area is accessed which is not in memory,
 * it does the actual mapping between kernel and user space memory
 */
struct page *mmap_nopage(struct vm_area_struct *vma, unsigned long address, int *type)
{
	struct page *page;
	struct mmap_info *info;
	/* is the address valid? */
	if (address > vma->vm_end) {
		printk("invalid address\n");
		//return NOPAGE_SIGBUS;
		return NULL;
	}
	/* the data is in vma->vm_private_data */
	info = (struct mmap_info *)vma->vm_private_data;
	if (!info->data) {
		printk("no data\n");
		return NULL;	
	}

	/* get the page */
	page = virt_to_page(info->data);
	
	/* increment the reference count of this page */
	get_page(page);
	/* type is the page fault type */
	if (type)
		*type = VM_FAULT_MINOR;

	return page;
}

struct vm_operations_struct mmap_vm_ops = {
	.open =     mmap_open,
	.close =    mmap_close,
	//.nopage =   mmap_nopage,
};

int my_mmap(struct file *filp, struct vm_area_struct *vma)
{
	vma->vm_ops = &mmap_vm_ops;
	vma->vm_flags |= (VM_DONTEXPAND | VM_DONTDUMP);
	/* assign the file private data to the vm private data */
	vma->vm_private_data = filp->private_data;
	mmap_open(vma);
	return 0;
}
int my_close(struct inode *inode, struct file *filp)
{
	struct mmap_info *info = filp->private_data;
	/* obtain new memory */
	free_page((unsigned long)info->data);
    	kfree(info);
	filp->private_data = NULL;
	return 0;
}

int my_open(struct inode *inode, struct file *filp)
{
	struct mmap_info *info = kmalloc(sizeof(struct mmap_info), GFP_KERNEL);
	/* obtain new memory */
    	info->data = (char *)get_zeroed_page(GFP_KERNEL);
	memcpy(info->data, "hello from kernel this is file: ", 32);
	memcpy(info->data + 32, filp->f_dentry->d_name.name, strlen(filp->f_dentry->d_name.name));
	/* assign this info struct to the file */
	filp->private_data = info;
	return 0;
}

static const struct file_operations my_fops = {
	.open = my_open,
	.release = my_close,
	.mmap = my_mmap,
};
static int __init hello_init(void)
{
    file1 = debugfs_create_file("mmap_example", 0644, NULL, NULL, &my_fops);

        //ierr = MPI_Finalize();
    //dev_get_by_name(snull_devs[0], "eth0");
    //printk(KERN_INFO "after get eth0 name\n");
    //if (snull_devs[0] == NULL)
      //return 0;

    /*
    int ret = 0;
    struct siginfo info;
    memset(&info, 0, sizeof(struct siginfo));
    info.si_signo = SIG_TEST;
    info.si_code = SI_QUEUE;
    info.si_int = 1234;  

    send_sig_info(SIG_TEST, &info, t);
    */
    /*
    memset(&sinfo, 0, sizeof(struct siginfo));
    sinfo.si_signo = SIGIO;
    sinfo.si_code = SI_USER;
    pid = 2899; // Everytime a new PID
    //task = find_task_by_vpid(pid); // I am also working on new and   old version of UBUNTU so thats why this is here
    task = pid_task(find_vpid(pid), PIDTYPE_PID);
    printk("%d .\n", task);
    if(task == NULL) {
      printk("Cannot find PID from user program\r\n");
      return 0;
    }
    send_sig_info(SIGIO, &sinfo, task);
    */
    
    printk(KERN_INFO "end init\n");
    return 0;
}
static void __exit hello_exit(void)
{
  debugfs_remove(file1);
  tasklet_kill (&my_tasklet);
  remove_proc_entry("hello_world", NULL);
  //int ret;
  //ret = cancel_delayed_work(&test_dwq);
  //flush_workqueue(test_wq);
  //destroy_workqueue(test_wq);
  //printk(KERN_INFO "Goodday! ret=%d\n", ret); 
  printk("Unloading hello.\n");
  return;
}

module_init(hello_init);
module_exit(hello_exit);
 
Old 08-05-2014, 09:26 AM   #4
a4z
Senior Member
 
Registered: Feb 2009
Posts: 1,727

Rep: Reputation: 742Reputation: 742Reputation: 742Reputation: 742Reputation: 742Reputation: 742Reputation: 742
you do not have the problem in the module,or?


so I just compile the user space code to see if there is a problem

Quote:
gcc -c userspace.c

somecode.c: In function 'icmp':
somecode.c:30:27: warning: assignment makes pointer from integer without a cast [enabled by default]
icmp(uint_least32_t *a) {a=*a-*1[&a];}
^
somecode.c: In function 'strcat2':
somecode.c:45:31: warning: incompatible implicit declaration of built-in function 'strlen' [enabled by default]
uint_least32_t dest_len = strlen(dest2);
^
somecode.c: In function 'main':
somecode.c:112:5: warning: incompatible implicit declaration of built-in function 'memcpy' [enabled by default]
memcpy(address + 11 , "*user*", 6);

it compiles here, with some warnings,

I think your gcc should do the same,
the output will look different

so what happens if you copy and past the code in the thread into a file.c
and compile it, (gcc -c file.c)
still this problem?


did you download and trial compile this
http://people.ee.ethz.ch/~arkeller/l...de/mmap_user.c

Last edited by a4z; 08-05-2014 at 09:28 AM.
 
  


Reply

Tags
kernel module, mmap



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
[SOLVED] Problem with repositories. (Dynamic MMap ran out of room) nicolasjengler Linux - Newbie 4 04-18-2011 03:30 PM
mmap busyfire Linux - Newbie 0 03-16-2009 06:19 AM
mmap for device file problem? advanxiang Linux - Software 0 04-16-2008 03:21 AM
Apt Dynamic MMap problem jsburger Linux - Software 1 08-19-2006 05:24 PM
mmap problem os2 Programming 4 06-21-2004 05:24 PM

LinuxQuestions.org > Forums > Non-*NIX Forums > Programming

All times are GMT -5. The time now is 10:35 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration