2019-05-23 18:34:39 hanp_linux 阅读数 728
  • 学会在Linux上编译调试C++项目

    本课程主要针对没有或者很少写过linux上C++程序的同学, 本课程会教你如何从0基础开始,安装配置ubuntu虚拟机、使用GCC编译普通程序、动态库、静态库,编写复杂项目配置文件makefile,使用GDB工具调试C++程序。

    13476 人正在学习 去看看 夏曹俊

本系列导航
(一)初识Linux驱动
(二)Linux设备驱动的模块化编程
(三)写一个完整的Linux驱动程序访问硬件并写应用程序进行测试
(四)Linux设备驱动之多个同类设备共用一套驱动
(五)Linux设备驱动模型介绍
(六)Linux驱动子系统-I2C子系统
(七)Linux驱动子系统-SPI子系统
(八)Linux驱动子系统-PWM子系统
(九)Linux驱动子系统-Light子系统
(十)Linux驱动子系统-背光子系统
(十一)Linux驱动-触摸屏驱动

1. Linux设备驱动的分类

Linux内核驱动按照访问方式,可以分为以下三类:
字符设备驱动
字符设备是能够像访问字节流(类似文件)的方式一样被访问的设备,最终在文件系统中以设备文件的形式存在。
常见的字符设备:鼠标、键盘(IO设备),LCD、Camera(帧缓冲设备)等。
块设备驱动
块设备和字符设备的区别在于内核内部管理数据的方式,块设备的访问方式是按照块进行随机访问的。
常见的块设备:磁盘、flash等存储设备。
网络设备驱动
如网卡。

2. Linux字符设备驱动框架

1). 设备号

Linux内核中有很多的字符设备驱动,内核是如何区分它们的? 每个字符设备都有一个唯一的标识 – 设备号
设备号的本质: 32位的无符号整数(dev_t)
设备号由两部分组成:
  1 – 高12位称为主设备号,表明这个设备属于哪一类设备。
  2 – 低20位成为次设备号,表明这个设备是同类设备中得具体哪一个。

设备号的申请方法:
第一种方法:静态定义并注册设备号
首先查看系统中有哪些设备号没有被分配给具体的设备,然后确定一个给当前的设备使用(cat /proc/devices可以看哪些号被占用了),定义方法如下:
dev_t devno = 主设备号<<20 | 次设备号;
或者使用系统接口进行组合

int maj = xx, int min = xx; 
dev_t devno = MKDEV(maj, min);

注册设备号 – 使申请的设备号生效并保证设备号在Linux内核中的唯一性
使用下面的接口:

int register_chrdev_region(dev_t from, unsigned count, const char *name)

参数:
from: 要注册的设备号
count:要注册多少个设备号,例如count = 3, 主设备号是255, 次设备号是0,那么将按照顺序依次注册三个设备号,分别是(主:255,从:0)、(255,1)、(255,2)
name:给要注册的设备命名,注册成功可以通过cat /proc/devices查看到
第二种方法:动态申请并注册设备号
此方法无需自己去确定哪个设备号可用,内核会查询哪个设备号没有被使用,然后分配给当前驱动进行注册,所以大部分驱动都采用这种注册方法,使驱动更加具有通用性(如果用静态注册,你选的设备号在当前设备上没有使用,但是当这个驱动移植到其他的设备上,可是其他设备上的某个驱动也使用的这个这个设备号,那么这个驱动就会注册失败)。
函数原型:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,  const char *name) 

功能:申请一个或多个设备号并进行注册
参数:
dev:要注册的设备的设备号,输入参数,内核会寻找没有使用的设备号,然后填充到这个dev参数中。
baseminor:主设备号由内核来确定,次设备号由我们自己去确定,baseminor就对应要申请的设备号的次设备号。
count:可以一次申请多个设备号,count表示要申请的设备号的数量,当申请多个设备号时,他们的主设备号一致,次设备号会在baseminor的基础上依次加1。
name:要注册的设备的名字,注册成功可以通过cat /proc/devices查看到

最后,无论通过哪种方式注册的设备号,在卸载模块的时候都需要将注册的设备号资源进行释放:

void unregister_chrdev_region(dev_t from, unsigned count)

功能:释放一个已经注册的设备号
参数:
from:要释放的设备号
count:要一次释放的设备号的数量,当释放多个设备号时,系统会从from开始,依次加1作为新的设备号进行释放

2).字符设备操作集合 – file_operations结构体

设备驱动有各种各样的, 鼠标驱动需要获取用户的坐标以及单双击动作、LCD驱动需要写framebuffer等等,但是对上层开发调用这些驱动的人来说,他们可能不懂也不关心底层设备是如何工作的,为了简化上层应用的操作,驱动程序给上层提供了统一的操作接口–open、read、write等,这样,对应做应用开发的人来说,不管你是什么样的设备,我只需要去打开(open)你这个设备,然后进行读写等操作就可以操作这个设备了。那么,驱动程序如何实现这样统一的接口呢?需要实现下面的file_operations结构体:

struct file_operations {
       struct module *owner;
       ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
       ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
       int (*open) (struct inode *, struct file *);
       int (*release) (struct inode *, struct file *);
       ...
} 

这里只列出了几个最基本的成员:
owner:一般填充THIS_MODULE,表示这个驱动(模块)归自己所有(这个概念对于初学者可能难以理解,到后面我会继续说明)
open:打开设备驱动的函数指针
release:关闭设备驱动的函数指针,为了对每个设备驱动的访问保护,所以用户必须先打开设备,才能对设备进行读写等操作,操作完必须再关掉。
read:读设备驱动的函数指针(比如用户可以通过read接口读取按键驱动的按键状态等)
write:写设备驱动的函数指针(比如用户可以通过write接口写LCD驱动的framebuffer显存,将画面显示再lcd上)
用法:
定义一个属于自己设备的操作集合xxx_fops,xxx通常命名为设备的名字,例如lcd_fops, key_fops等。

struct file_operations  xxx_fops ={  
       .owner   = THIS_MODULE,   //表示这个模块为自己所有
       .open    = xxx_open,      //当用户调用open接口时,内核就会根据系统调用来调用对应的xxx_fops里面的xxx_open函数(xxx表示自己命名)
       .release = xxx_close,       
       .read    = xxx_read,
       ...
};

xxx_open、xxx_read等函数需要自己去实现,根据不同的驱动,去做不同的事情,从而达到了不同的驱动给上层提供统一的接口。

3). 字符设备的核心 – cdev结构体

分配、设置、注册cdev结构体
内核用cdev结构体来表示一个字符设备,所以每个字符设备驱动,都需要注册一个cdev结构体

struct cdev {	
	struct kobject kobj;
  	struct module *owner;
  	const struct file_operations *ops;
  	struct list_head list;
  	dev_t dev;
  	unsigned int count;
};

owner:一般填充THIS_MODULE,表示这个驱动(模块)归自己所有。
ops:对应这个设备的文件操作集合。
list:内核中有很多字符设备,每个设备对应一个自己的cdev,这些cdev通过这个list连在一起,当注册一个新的cdev时,就会通过cdev里面的list挂到内核的cdev链表上。
count:同类设备,可以一次注册多个cdev,但是他们的操作方法(fops)是一样的,比如usb设备,多个usb共用一套操作方法(fops),但是每个usb都有自己的cdev。

分配(创建)cdev

struct cdev cdev;

设置(初始化)cdev,函数原型:

void cdev_init(struct cdev *, const struct file_operations *);

使用:

cdev_init(&cdev, &xxx_fops);

注册cdev结构体 – 添加一个字符设备(cdev)到系统中,函数原型:

int cdev_add(struct cdev *p, dev_t dev, unsigned count)

@p: 要注册的cdev结构体。
@dev: 第一个设备号。
@count: 与此设备对应的连续的次设备号的数量 – 也就是要注册的cdev的数量,当count > 1时,会向系统注册多个cdev结构体,这些个cdev的fops是同一个,但是设备号的次设备号不同。
使用:

dev_add(&cdev, devno, 1);

有注册同样也有释放:

void cdev_del(struct cdev *p)
cdev_del(&cdev);

完整代码:hello.c

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>

dev_t devno;
int major = 255;

const char DEVNAME[] = "hello_device";

/* 2. 分配file_operations结构体 */
struct file_operations hello_fops = {
    .owner = THIS_MODULE,
};

struct cdev cdev;

static int hello_init(void)
{
    int ret;
    printk("%s : %d\n", __func__, __LINE__);
	
    /* 1. 生成并注册设备号 */
    devno = MKDEV(major, 0);
    ret = register_chrdev_region(devno, 1, DEVNAME);
    if (ret < 0)
    {
        printk("%s : %d fail to register_chrdev_region\n", __func__, __LINE__);
        return -1;
    }

    /* 3. 分配、设置、注册cdev结构体 */
    cdev.owner = THIS_MODULE;
    cdev_init(&cdev, &hello_fops);
    ret = cdev_add(&cdev, devno, 1);
    if (ret < 0)
    {
        printk("%s : %d fail to cdev_add\n", __func__, __LINE__);
        return -1;
    }
    return 0;
}

static void hello_exit(void)
{
    printk("%s : %d\n", __func__, __LINE__);
    
    /* 释放资源 */
    cdev_del(&cdev);
    unregister_chrdev_region(devno, 1);
}

MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

Makefile

KERNEL_PATH := /lib/modules/`uname -r`/build
PWD := $(shell pwd)
MODULE_NAME := hello

obj-m := $(MODULE_NAME).o

all:
	$(MAKE) -C $(KERNEL_PATH) M=$(PWD)

clean:
	rm -rf .*.cmd *.o *.mod.c *.order *.symvers *.tmp *.ko

测试:
make生成hello.ko
sudo insmod hello.ko //如过出现下面的log,

insmod: ERROR: could not insert module hello.ko: File exists

说明之前安装的没有卸载,需要先卸载然后再安装新的hello.ko。
lsmod | grep hello //如果打印出
说明模块没有问题,已经装载到系统中。
cat /proc/devices | grep hello //打印出
说明字符设备注册成功,255是注册的设备号,hello_device是注册的名字
最后别忘了卸载设备:sudo rmmod hello
有些同学会发现,明明卸载完了,但是在安装hello.ko的时候,提示:
ERROR: could not insert module hello.ko: Operation not permitted,那么就有可能是你的代码中设备号没有释放或者cdev没有释放,需要检查代码,修改后重启电脑再次安装即可。

3. 实现文件操作集合

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

dev_t devno;
int major = 255;
const char DEVNAME[] = "hello_device";
char data[64]  = "Hello world!";

int hello_open(struct inode * ip, struct file * fp)
{
    printk("%s : %d\n", __func__, __LINE__);
    
    /* 一般用来做初始化设备的操作 */
    return 0;
}

int hello_close(struct inode * ip, struct file * fp)
{
    printk("%s : %d\n", __func__, __LINE__);
    
    /* 一般用来做和open相反的操作,open申请资源,close释放资源 */
    return 0;
}

ssize_t hello_read(struct file * fp, char __user * buf, size_t count, loff_t * loff)
{
    int ret;
    
    /* 将用户需要的数据从内核空间copy到用户空间(buf) */
    printk("%s : %d\n", __func__, __LINE__);
    if ((ret = copy_to_user(buf, data, count)))
    {
        printk("copy_to_user err\n");
        return -1;
    }
    return count;
}

ssize_t hello_write(struct file * fp, const char __user * buf, size_t count, loff_t * loff)
{
    int ret;
    
    /* 将用户需要的数据从内核空间copy到用户空间(buf) */
    printk("%s : %d\n", __func__, __LINE__);
    if ((ret = copy_from_user(data, buf, count)))
    {
        printk("copy_from_user err\n");
        return -1;
    }
    return count;
}

/* 2. 分配file_operations结构体 */
struct file_operations hello_fops = {
    .owner = THIS_MODULE,
    .open  = hello_open,
    .release = hello_close,
    .read = hello_read,
    .write = hello_write
};
struct cdev cdev;

static int hello_init(void)
{
    int ret;
    printk("%s : %d\n", __func__, __LINE__);
    
    /* 1. 生成并注册设备号 */
    devno = MKDEV(major, 0);
    ret  = register_chrdev_region(devno, 1, DEVNAME);
    if (ret != 0)
    {
        printk("%s : %d fail to register_chrdev_region\n", __func__, __LINE__);
        return -1;
    }
    
    /* 3. 分配、设置、注册cdev结构体 */
    cdev.owner = THIS_MODULE;
    ret = cdev_add(&cdev, devno, 1);
    cdev_init(&cdev, &hello_fops);
    if (ret < 0)
    {
        printk("%s : %d fail to cdev_add\n", __func__, __LINE__);
        return -1;
    }
    printk("success!\n");
    return 0;
}

static void hello_exit(void)
{
    printk("%s : %d\n", __func__, __LINE__);
      
    /* 释放资源 */
    cdev_del(&cdev);
    unregister_chrdev_region(devno, 1);
}

MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

注意:
hello_read是将内核空间的数据copy到用户空间,hello_write是将用户数据copy到内核空间。用户空间(应用程序)和内核空间(驱动)数据的交互一定要用copy_to_user和copy_from_user。在read和write中还应该判断参数的合法性,比如传入的count是负数肯定是非法的,这一步也很重要,但是为了代码简洁就没加。这只是个框架,以后根据具体的驱动,再在函数体里填充具体的操作。

4. 写应用程序测试驱动 app.c

#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

int main(char argc, char * argv[])
{
    int fd;
    int ret;
    char buf[64];
    
    /* 将要打开的文件的路径通过main函数的参数传入 */
    if (argc != 2)
    {
        printf("Usage: %s <filename>\n", argv[0]);
        return -1;
    }
    
    fd = open(argv[1], O_RDWR);
    if (fd < 0)
    {
        perror("fail to open file");
        return -1;
    }
    
    /* read data */
    ret = read(fd, buf, sizeof(buf));
    if (ret < 0)
    {
        printf("read err!");
        return -1;
    }
    printf("buf = %s\n", buf);
    
    /* write data */
    ret = write(fd, buf, sizeof(buf));
    if (ret < 0)
    {
        printf("read err!");
        return -1;
    }
    
    close(fd);
    return 0;
}

前面说过,用户访问字符设备驱动最终以字符设备文件的形式访问的,所以测试无非就是写一个应用程序,去open、read、write这个对应的设备节点,然后通过系统调用去访问驱动里的fops对应函数。
测试:
安装驱动:sudo insmod hello.ko
查看是否安装成功:cat /proc/devices 查找对应设备号和名字
创建设备节点和设备挂钩:sudo mknod /dev/hello c 255 0
当我们执行insmod后驱动就被安装到了内核中,但是我们要想访问驱动,必须先创建设备节点,通过设备节点来访问驱动,设备节点其实就是个文件,文件类型是c–字符设备文件。
/dev/hello:要创建的设备节点的名字及路径,一般都在/dev目录下创建。
c: 表示要创建一个字符设备。
255 0:主设备号和次设备号,表示创建的这个设备节点和对应设备号是(255,0)的这个设备关联,这样访问这个设备节点就可以通过设备号唯一确定一个设备了。
ls -l /dev/hello 可以看到这个设备节点的详细信息

crw-r--r-- 1 root root 255, 0 1126 19:40 /dev/hello

在命令行编译并执行应用程序进行测试:

 gcc app.c -o app       //生成用户空间的可执行程序app
 ./app  /dev/hello       //执行生成的可执行程序app,并传入参数

打印出了我们从驱动中读到的数据:

 buf = Hello world!

然后执行dmesg,可以看到驱动的执行过程log:

 [12752.386888] hello_init : 83
 [12752.386891] success!
 [12948.418264] hello_open : 21
 [12948.418269] hello_read : 42
 [12948.418286] hello_write : 58
 [12948.418322] hello_close : 30

5. 画框图解释从应用层访问到驱动的过程

首先了解几个概念:
在写驱动的时候,实现open和close函数都有两个重要的参数struct inode和struct file结构体。
inode结构体:

struct inode {
    unsigned int  i_flags;
    dev_t       i_rdev;
    ...
 }

一切皆文件,用户在文件系统下看到和操作的都是文件,但是这个文件对应在内核中是以一个inode结构体的形式存在的,当我们在文件系统下用touch或者mknod等命令创建文件时,内核都会创建唯一一个与之对应的inode结构体,保存这个文件的基本信息,当我们用户操作这个文件的时候,操作系统(内核)其实操作的是对应的inode结构体,会将我们的访问需求转换为对某个方法的调用,根据你打开的文件的类型进行不同的操作。
file结构体:

struct file {
    struct inode  *f_inode; /* cached value */
    const struct file_operations *f_op;
    unsigned int   f_flags;
    void       *private_data;
    ...
}

操作系统将用户对某个文件的访问的需求转换为对某个方法的调用,内核根据你打开的文件的类型进行不同的操作,当用户打开某个文件时,实际上内核操作的是这个文件对应的inode结构体,同时内核会创建一个file结构体与之对应,这个file结构体里面保存了用户对这个文件(inode)结构体的操作信息(操作哪个文件:inode;以什么方式打开的,R/W/RW等:f_flags)。
总结:
也就是说,inode结构体和文件是一一对应的关系,每个文件在内核系统中都有一个唯一的inode结构体与之对应。只有在用户对文件进行打开操作的时候,内核空间才会创建一个file结构体,那么当多个用户对同一个文件进行打开时,就会创建多个file结构体,分别保存每个用户的操作,file结构体和文件是多对一的关系。
在这里插入图片描述

6. 自动创建设备节点

在实际的项目场景中,不可能每次开机让用户自己手动去创建设备节点然后装载,所以需要我们在代码中自动创建设备节点。在所有的初始化完成并成功之后加上如下:

struct class *hello_class;
hello_class = class_create(THIS_MODULE, "hello");  //hello:会在/sys/class这个目录下创建以hello为名的类,表示注册的这个设备属于hello这个类
device_create(hello_class, NULL, devno, NULL, "hello device");  //devno是对应注册的设备号, hello device就是内核自动在/dev目录下创建的设备节点的名字

同样,在卸载设备的时候,也要卸载这个设备节点:

device_destroy(hello_class, devno);
class_destroy(hello_class);

例;

struct class *hello_class;

static int hello_init(void)
{
    /* 1. 生成并注册设备号 */
    /* 2. 分配file_operations结构体 */
    /* 3. 分配、设置、注册cdev结构体 */  
    ...  
  
    hello_class = class_create(THIS_MODULE, "hello"); 
    device_create(hello_class, NULL, devno, NULL, "hello device");  
  
    return 0;
}

static void hello_exit(void)
{
    /* 释放资源 */
    ...
    
    device_destroy(hello_class, devno);
    class_destroy(hello_class);
}

编译安装完驱动后:

 /dev$ ls -l hello_device
 crw------- 1 root root 255, 0  218 18:02 hello_device
2019-07-11 00:07:37 weixin_38102771 阅读数 170
  • 学会在Linux上编译调试C++项目

    本课程主要针对没有或者很少写过linux上C++程序的同学, 本课程会教你如何从0基础开始,安装配置ubuntu虚拟机、使用GCC编译普通程序、动态库、静态库,编写复杂项目配置文件makefile,使用GDB工具调试C++程序。

    13476 人正在学习 去看看 夏曹俊

一. 前言

        在之前的博客介绍了一个简单 Linux 服务器程序的实现,光有服务器没有客户端是无法判断服务器的功能是否可用的,本文我们就写一个简单的客户端程序,可以与服务器进行简单的交互。

 

二. 简单Linux客户端程序

1. TCP客户端程序实现流程

        之前的 TCP 服务器程序是监听 8080 端口,只要客户端发送数据过去就往客户端回复 "Hello World",如果想要与服务器建立连接并通信:我们需要创建一个 socket 套接字,调用 connect 与服务器建立起连接,之后再通过 read,write 进行数据读写交互,流程如下图。

 

2. TCP客户端程序的代码实现

#include <stdio.h>                                                                                                                  
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <string.h>
#include <ctype.h>

#define SERVER_ADDR     "127.0.0.1"
#define SERVER_PORT     8080

int main()
{
    int cfd = 0;
    struct sockaddr_in saddr;
    char buf[BUFSIZ] = {0};
    int i = 0,n = 0;

    cfd = socket(AF_INET,SOCK_STREAM,0);

    bzero(&saddr,sizeof(saddr));
    saddr.sin_family = AF_INET;
    inet_pton(AF_INET,SERVER_ADDR,&saddr.sin_addr.s_addr);
    saddr.sin_port = htons(SERVER_PORT);

    connect(cfd,(struct sockaddr*)&saddr,sizeof(saddr));
    printf("已经成功发起了连接!\n");

    while(1)
    {   
        fgets(buf,sizeof(buf),stdin);
        if(strncmp(buf,"quit",4) == 0)
        {   
            break;
        }   
        write(cfd,buf,strlen(buf));
        memset(buf,0,sizeof(buf));
        n = read(cfd,buf,sizeof(buf));
        for(i=0;i<n;++i)                                                                                                            
        {   
            buf[i] = toupper(buf[i]);
        }   
        printf("%s",buf);
        memset(buf,0,sizeof(buf));
    }

    close(cfd);

    return 0;
}

 程序讲解

1. 首先调用一个 socket 函数创建一个套接字,第一个参数 AF_INET 代表是 IPv4 地址,第二个参数 SOCK_STREAM 代表流式协议,第三个参数 0 代表默认协议,这里是 TCP。(因为服务器是 TCP 通信的,所以我们使用的是 SOCK_STREAM )

2. 创建好套接字后,我们需要指定好要连接的服务器 IP 和端口(IP + Port 标识网络中的一个进程,这些信息存放在一个struct sockaddr_in 类型的结构体中,然后将该结构体传入给 connect 函数调用即可,struct  sockaddr_in 的类型结构如下。

struct sockaddr_in {
    short            sin_family;   // e.g. AF_INET
    unsigned short   sin_port;     // e.g. htons(3490)
    struct in_addr   sin_addr;     // see struct in_addr, below
    char             sin_zero[8];  // zero this if you want to
};
struct in_addr {
    unsigned long s_addr;  // load with inet_aton()
};

sin_family:指定地址类型,AF_INET 代表 IPv4,AF_INET6 代表 IPv6。

sin_port:指定服务器的端口号,需要使用网络字节序,所以参数要使用 htons 函数将本机字节序转换为网络字节序。

sin_addr:其实是一个 unsigned long 的 32 位整数,我们需要使用 inet_pton 或者 inet_aton 将点分十进制的 IP 地址转化为一个 unsigned long 类型的整数。

sin_zero:置为 0 即可。

初始化 struct sockaddr_in 类型的结构体变量之后,我们就可以调用 connect 函数与服务器建立连接了。

connect 函数原型:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

第一个参数传入使用 socket 函数创建套接字返回的句柄,第二个参数类型是 const struct sockaddr *,所以我们需要将 &saddr 的地址类型做一个强制转换,第三个参数指定 saddr 的大小即可。

关于上述一些 API 的具体含义将会在后续文章做深入讲解,这里只需要先简单了解含义即可。

3. 打印出 "已经成功发起了连接后",程序进入一个 while(1) 死循环中,每次我们从终端输入字符串后,客户端就调用 write 将该字符串发送给服务器,然后调用 read 等待服务器回复信息。服务器收到字符串后,就往客户端回复 "Hello World",此时客户端收到了 "Hello World",就将 "Hello World" 转换为全部大写的,最后再次等待终端输入字符串。

 

3. UDP客户端程序的代码实现

         因为暂时没有讲解 UDP 服务器的实现,后续再补充这部分的内容。

 

三. 结束

         这里的客户端程序实现是比较粗糙的,没有进行过多的程序返回值判断,例如 connect 调用失败就不该让流程往下走,而且 write,read 并不能保证一定调用成功。之后我们会具体讲解 socket,connect,listen,accept 等函数,到那时候我们再让我们的客户端程序更加完善。

 

2018-06-26 16:11:03 Xiao13Yu14 阅读数 521
  • 学会在Linux上编译调试C++项目

    本课程主要针对没有或者很少写过linux上C++程序的同学, 本课程会教你如何从0基础开始,安装配置ubuntu虚拟机、使用GCC编译普通程序、动态库、静态库,编写复杂项目配置文件makefile,使用GDB工具调试C++程序。

    13476 人正在学习 去看看 夏曹俊

今天尝试使用Visual Studio 2017写Linux C++程序。这样就可以把我在Windows的Visual Studio 2017下实现的代码自动拷贝到实验室的Linux服务器上编译运行。

参考文档:

  1. Create a New Linux Project
  2. 知乎:如何在 Visual Studio 上用 C/C++ 写 Linux 程序?
  3. visual studio 2017搭建linux c++开发环境

遇到的问题及其解决方法:

  1. 并不是Windows下的整个工程目录的内容都会自动复制到Linux主机,默认只复制源代码文件。其他文件的复制需要自己配置好。(参考)

  2. Windows下和Linux下的文件路径表示不同,如果程序涉及到文件读写,需要注意路径的修改。比方说本来这样的路径D:\\Yuyan\\LinuxC++\\MOILS-linux\\MOILS-linux 应该改成/home/sysu502/projects/MOILS-linux

  3. 在Windows VS下可以正常运行的程序,在Linux下出现了Segmentation fault异常。最后发现原因是传入sort的比较函数写的不对。(C++ sort之Segmentation fault原因及其解决方法)

  4. VS 2017 许可证过期的解决方法

  5. ftp连接不到Linux服务器

  6. Xshell传文件:ubuntu系统下安装rz/sz命令及使用说明

  7. 添加或删除到Linux、Mac或Windows等远程系统的SSH连接:工具->选项->跨平台->连接管理器->添加->填写主机名、端口、用户名、密码->连接->确定

  8. ftp可以连接,但是上传不了文件,FlashFXP显示550 Permission denied。原因是服务器上的ftp配置文件没有配置好。通过vi /etc/vsftpd.conf编辑文件,编辑之前可能是readonly的文件,需要sudo chmod 777 /etc/vsftpd.conf添加权限才可以进行编辑。最后,重启ftp服务器service vsftpd restart

  9. Linux 查看 CPU 型号及内存频率及其它信息的命令

    cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c

Server-2: 40 Intel® Xeon® CPU E5-2640 v4 @ 2.40GHz
Server-3: 40 Intel® Xeon® CPU E5-2640 v4 @ 2.40GHz
Server-4: 40 Intel® Xeon® CPU E5-2640 v4 @ 2.40GHz
Server-5: 48 Intel® Xeon® Gold 5118 CPU @ 2.30GHz

  1. 某个工程要包含其他工程的代码
    VC++ 目录 -> 包含目录在这里插入图片描述
    C/C++ 常规 -> 附加包含目录 在这里插入图片描述
    包含了这些目录之后,也有可能会报错,这时候不要一直怀疑是目录链接等问题,而是应该看VS的error,error通常来说有很多,但是关键的就是几个,把关键的解决了就好。另外,要特别注意,error还可能藏着一堆warning中间。

  2. 无法启动远程调试
    通常是因为你第一次是生成解决方案后把项目从远程主机上删除了,这时候重新生成解决方案就好。

2015-10-29 10:51:31 onetwothreef 阅读数 5494
  • 学会在Linux上编译调试C++项目

    本课程主要针对没有或者很少写过linux上C++程序的同学, 本课程会教你如何从0基础开始,安装配置ubuntu虚拟机、使用GCC编译普通程序、动态库、静态库,编写复杂项目配置文件makefile,使用GDB工具调试C++程序。

    13476 人正在学习 去看看 夏曹俊

本文代码参考ZFZF294990051 童鞋的代码,非常感谢ZFZF294990051童鞋。

参考地址:http://blog.csdn.net/zfzf294990051/article/details/17322621

#include <stdio.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>

#include "ql_commfunc.h"
#define I2C_FILE_NAME "/dev/i2c-0"



static int get_i2c_register(int file,
                            unsigned short addr,
                            unsigned char reg,
                            unsigned char *val,
				int len) {
    //unsigned char inbuf, outbuf;
    unsigned char outbuf;
    struct i2c_rdwr_ioctl_data packets;
    struct i2c_msg messages[2];

    /*
     * In order to read a register, we first do a "dummy write" by writing
     * 0 bytes to the register we want to read from.  This is similar to
     * the packet in set_i2c_register, except it's 1 byte rather than 2.
     */
    outbuf = reg;
    messages[0].addr  = addr;
    messages[0].flags = 0;
    messages[0].len   = sizeof(outbuf);
    messages[0].buf   = &outbuf;

    /* The data will get returned in this structure */
    messages[1].addr  = addr;
    messages[1].flags = I2C_M_RD/* | I2C_M_NOSTART*/;
    messages[1].len   = len;
    messages[1].buf   = val;

    /* Send the request to the kernel and get the result back */
    packets.msgs      = messages;
    packets.nmsgs     = 2;
    if(ioctl(file, I2C_RDWR, &packets) < 0) {
        perror("Unable to send data");
        return 1;
    }
    //*val = inbuf;

    return 0;
}


static int set_i2c_register(int file,
                            unsigned char addr,
                            unsigned char reg,
                            unsigned char *value,
			    	int len) {

    unsigned char *outbuf = (unsigned char *)malloc(sizeof(unsigned char)*(len+1));
	if(outbuf==NULL)
	{
		perror("MALLOC");
		return -1;
	}
    struct i2c_rdwr_ioctl_data packets;
    struct i2c_msg messages[1];

    messages[0].addr  = addr;
    messages[0].flags = 0;
    messages[0].len   = len+1;	
    messages[0].buf   = outbuf;
	

    /* The first byte indicates which register we'll write */
    outbuf[0] = reg;

    /* 
     * The second byte indicates the value to write.  Note that for many
     * devices, we can write multiple, sequential registers at once by
     * simply making outbuf bigger.
     */
//    outbuf[1] = value;
memcpy(outbuf+1, value, len);

    /* Transfer the i2c packets to the kernel and verify it worked */
    packets.msgs  = messages;
    packets.nmsgs = 1;
    if(ioctl(file, I2C_RDWR, &packets) < 0) {
        perror("Unable to send data");
        return 1;
    }

    return 0;
}



int ql_get_i2c_register(unsigned short slave_addr, unsigned char reg, unsigned char *outbuf, int buf_len )
{
	
	int fd;
	// Open a connection to the I2C userspace control file.
	if ((fd = open(I2C_FILE_NAME, O_RDWR)) < 0) {
		PERROR("Unable to open i2c control file");
		return -1;
        	//exit(1);
	}
	if(get_i2c_register(fd, slave_addr, reg, outbuf, buf_len))
	{
		DEBUG("");
		return -2;
	}
	close(fd);
	return 0;
}

int ql_set_i2c_register(unsigned short slave_addr, unsigned char reg, unsigned char *inbuf, int buf_len )
{
	int fd;
        // Open a connection to the I2C userspace control file.
        if ((fd = open(I2C_FILE_NAME, O_RDWR)) < 0) {
                PERROR("Unable to open i2c control file");
                return -1;
                //exit(1);
        }
        if(set_i2c_register(fd, slave_addr, reg, inbuf, buf_len))
        {
                DEBUG("");
                return -4;
        }
        close(fd);
        return 0;
	
}


2017-03-09 23:21:50 yancola 阅读数 10932
  • 学会在Linux上编译调试C++项目

    本课程主要针对没有或者很少写过linux上C++程序的同学, 本课程会教你如何从0基础开始,安装配置ubuntu虚拟机、使用GCC编译普通程序、动态库、静态库,编写复杂项目配置文件makefile,使用GDB工具调试C++程序。

    13476 人正在学习 去看看 夏曹俊

在linux编写shell程序并执行的步骤

简介

我是第一次写博客,不知道应该有什么格式和注意事项,请大家多多指教。

今天是要讲讲怎样在linux命令行环境下创建一个脚本程序并运行的。

这对于稍有经验的人来说都不是事,但对于初次接触linux的新手来说可能会遇到一点麻烦,所以我今天详细讲讲。

进入vim编辑器,写代码

vi编辑器是所有linux/UNIX操作系统中的标准编辑器,也是目前linux系统最基本的文本编辑器,主要工作在字符模式下,
由于不需要图形界面支持,因此它的效率很高。
怎么进入vim呢?在命令行中输入 vim 文件名

例如,我要创建一个helloworld文件就输入

vim helloworld


回车后就进入了编辑模式。下图就是编辑模式



但是现在还不能直接输入,要按一下键盘上的i,注意到左下角的文件名变成了INSERT,这就可以开始,敲代码了,如图



保存文件,推出编辑

怎样保存文件退出呢?先按下esc,再按冒号,最后输入wq,回车,如图



执行

执行的时候用命令 bash 文件名,在本例中就是
bash helloworld
执行结果如图


如果你想用

./helloworld

这种方式执行,需要先赋予用户可执行权限,如下

chmod u+x helloworld

chmod 命令可以修改目录和文件的访问权限,u代表文件的所有者,+代表添加后面的权限,x代表可执行权限 ,如图


到这里,整个过程就讲解完了。

第一次写博客,花了一个多小时,以后会写出更好的文章,请大家多多支持!




Linux计算程序耗时

阅读数 857

没有更多推荐了,返回首页