【嵌入式Linux】线程与进程

1. 查看源代码

1. 线程与进程

在一些操作中,等待的时间是比较长的,为避免卡顿,或者反应不及时引入线程和进程。

进程是资源分配的最小单位,线程是CPU调度的最小单位。

单进程单线程:一个人在一个桌子上吃菜。

单进程多线程:多个人在同一个桌子上一起吃菜。

多进程单线程:多个人每个人在自己的桌子上吃菜。

  • ubuntu默认没有pthread库,需要安装。
sudo apt-get install -y glibc-doc manpages-posix-dev
  • 查看pthrea手册,提示:
$ man pthreads
No manual entry for pthreads
#安装manpages
$ sudo apt-get install manpages-posix manpages-posix-dev

2. 编译各个程序并在Ubuntu运行;理解并解释运行状态和结果

2. 多线程

(1)线程的创建与终止

  • 函数
#include <pthread.h>  
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • 说明
参数用法
返回值成功:0; 失败:错误号 //Linux环境下,所有线程特点,失败均直接返回错误号。
pthread_t当前Linux中可理解为:typedef unsigned long int pthread_t;//无符号长整形
参数1传出参数,保存系统为我们分配好的线程ID
参数2通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
参数3函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。参数是函数指针,只能传递函数名,不能传递参数。所以就是只能有一个参数。
参数4线程主函数执行期间所使用的参数。
  • 编译并执行
gcc -o ptread pthread.c -lpthread
./ptread

pthread_fight

  • 由于线程之间的竞争可能导致每次输出不一样。
#include<unistd.h>
for (index = 0; index < array_num; index++) { /* 循环创建 5个线程 */
    printf("In main: creating thread %ld.\n", index);
    returned_code_err = pthread_create(&thr_num_array[index], NULL, thread_function, (void *) index); /* 创建线程 */
    sleep(1);         //创建线程后延时

debug_1

(2)进程的连接与分离

  • 函数
#include <pthread.h>
    int pthread_join(pthread_t thread, void **retval);
  • 说明
参数用法
参数1被等待的进程表示符
参数2一个用户定义的指针,可以用来存储被等待线程的返回值
  • 部分代码
for(index=0; index<NUM_THREADS; index++) 
{
    printf("Main: creating tid_array %ld\n", index);
    err = pthread_create(&tid_array[index], NULL, thread_func, (void *)index); /* 创建线程 */
    if (err) 
    {
        printf("ERROR; return code from pthread_create() is %d\n", err);
        exit(-1);
    }
    err = pthread_join(tid_array[index], &status); /*等待线程终止,并获取返回值*/
    if (err) 
    {
        printf("ERROR; return code from pthread_join() is %d\n", err);
        exit(-1);
    }
    printf("Main: completed join with tid_array %ld having a status of %ld\n",index,(long)status);
}
  • 编译并运行
gcc -o Untitled-2.out Untitled-2.c -lpthread -lm
./Untitled-2.out

pthrea_join

(3)使用互斥量保护多线程同时输出

  • 说明
函数声明作用
pthread_mutex_initint pthread_mutex_init(pthread_mutex_t mutex, const pthread_mutexattr_tmutexattr);初始化锁
pthread_mutex_lockint pthread_mutex_lock(pthread_mutex_t *mutex);阻塞上锁
pthread_mutex_trylockint pthread_mutex_trylock(pthread_mutex_t *mutex);非阻塞上锁
pthread_mutex_unlockint pthread_mutex_unlock(pthread_mutex_t *mutex);解锁
pthread_mutex_destroyint pthread_mutex_destroy(pthread_mutex_t *mutex);销毁锁,释放资源
  • 编译并运行
gcc -o Untitled-4.out Untitled-4.c -lpthread -lm
./Untitled-4.out

互斥锁,消除线程之间的竞争

(4)条件变量使用

条件变量用来阻塞一个线程,直到其他的线程通知它条件已经满足为止。

  • 说明
函数声明作用
pthread_cond_initint pthread_cond_init(pthread_cond_t cond, pthread_condattr_tcond_attr);初始化条件变量
pthread_cond_waitint pthread_cond_wait(pthread_cond_t cond, pthread_mutex_tmutex);阻塞等待
pthread_cond_timedwaitint pthread_cond_timedwait(pthread_cond_t cond, pthread_mutex_tmutex, const struct timespec *abstime);超时等待
pthread_cond_signalint pthread_cond_signal(pthread_cond_t *cond);唤醒一个等待该条件的线程
pthread_cond_destroyint pthread_cond_destroy(pthread_cond_t *cond);销毁条件变量
  • 编译并执行
gcc -o Untitled-5.out Untitled-5.c -lpthread -lm
./Untitled-5.out

pthread_cond

3. 多进程

(1)获取测试环境变量代码

  • 通过main()函数的第三个参数env循环获取
#include <stdio.h>
int main(int argc, char * argv[], char *env[])
{
    int i = 0;
    while (env[i])
    puts(env[i++]);
    return 0;
}

env

  • 通过environ全局变量获取
#include <stdio.h>
extern char ** environ;
int main(int argc, char * argv[])
{
    int i = 0;
    while (environ[i]) puts(environ[i++]);
    return 0;
}

environ

  • 通过getenv函数获取
#include <stdio.h>
#include <stdlib.h>

int main ()
{
    printf("PATH : %s\n", getenv("PATH"));
    printf("HOME : %s\n", getenv("HOME"));
    printf("ROOT : %s\n", getenv("ROOT"));
    printf("SHELL : %s\n", getenv("SHELL"));
    printf("USERNAME : %s\n", getenv("USERNAME"));
    printf("LANG : %s\n", getenv("LANG"));
    printf("XDG_SESSION_DESKTOP : %s\n", getenv("XDG_SESSION_DESKTOP"));
    printf("OLDPWD : %s\n", getenv("OLDPWD");  
   return(0);
}

getenv

(2)创建进程程序代码

  • fork()函数

fork()函数从运行着的进程中分裂出一个子进程,它通过拷贝父进程的方式创建子进程。子进程与父进程有相同的代码空间、文件描述符等资源。

  • 用法
#include <sys/types.h>
#include <unistd.h>
    pid_t fork(void);
  • 进程创建后,子进程与父进程开始并发执行,执行顺序由内核调度算法来决定。fork()函数如果成功创建了进程,就会对父子进程各返回一次,其中对父进程返回子进程的PID,对子进程返回0;失败则返回小于0的错误码。

  • 编译执行

gcc -o fork.out fork.c
./fork.out

fork

(3)子进程加载新程序代码

  • 原理

在创建进程后子进程与父进程有相同的代码空间;实际应用中,需要子进程 去执行另外一个程序,可以在子进程中调用exec族函数它的代码段完全替换为 新的程序,并从这个新进程的main函数开始执行。

  • exec函数族
函数后缀说明
execll表示使用list形式来传递新程序的参数,所有这些参数均以可变参数的形式在exec中给出,最后一个参数需要是NULL用以表示没有更多的参数了,如execl()函数。
execlee表示可以传递一个指向环境字符串指针数组的指针,环境数组需要以NULL结束,如execvpeO函数。而无此后缀的函数则将当前进程 的environ变量复制给新的程序,如execv()函数。
execlpp表示使用filename做参数,如果filename中包含则视其为路径名,否则将会在PATH环境变量所指定的各个目录中搜索该文件,如exedp()函数。无后缀p则必须使用路径名来指定可执行文件的位置, 如execl()函数。
execvv表示使用vector形式来传递新程序的参数,传给新程序的所有 参数放入一个字符串数组中,数组以NULL结束以表示结尾,如execv()函数。exec族函数只有在出错的时候才会返回,如果成功,该函数无返回,否则返 回-1。
  • 运行结果

exec

(4)使用daemon创建守护进程

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件,它不需要用户输入就能运行并提供某种服务。

守护进程的父进程是init进程,因为它真正的父进程在fork出该子进程后就先于核子进 程exit退出了,所以它是-个由init领养的孤儿进程。守护进程是非交互式程序,没有控制终端,所以任何输出(无论是向标准输出设备还是标准错误输出设备的输出)都需要特殊处理。

  • daemon()函数
#include <unistd.h>
int daemon(int nochdir, int noclose);
参数说明
nochdir如果传入0,则daemon函数将调用进程的工作目录设置 为根目录,否则保持原有的工作目录不变
noclose如果传入0,则daemon函数会将标准输入、标准输出、 标准错误重定向到/dev/null文件中,否则不改变这些文件描述符。该函数如果成功则返回0,否则返回-1,并设置errno
  • 编译并执行
gcc -o daemon.out daemon.c
./daemon.out
cat /tmp/daemon.log

daemon

(5)信号函数sigaction的使用

信号(signal),又称为软中断信号,用来通知进程发生了异步事件。进程之 间可以互相发送信号,内核也可以因为内部事件而给进程发送信号。注意,信号 的作用仅仅是通知进程发生了什么事件,并不向该进程传递任何数据。

  • 信号处理方式
序号处理方法
1使用类似中断的处理程序,对于需要处理的信号,进程可以指 定相应的处理函数,由该函数来负责对信号的处理
2忽略某个信号,对该信号不做任何处理,就像未发生过一样
3对该信号的处理保留系统默认值,这种缺省操作对于大部分的 信号来说就是使得进程被终止
  • sigaction函数
#include <signal.h>
    int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact);
参数说明
返回值函数成功返回0,否则返回-1
signum指出需要改变处理方法的信号,如SIGINT信号,但SIGKILL 和SIGSTOP这两个信号是不可捕捉的
act/oldactsigaction结构体的指针,act是要设置的对信号 的新处理方式,而oldact则为原来对信号的处理方式
  • 编译并执行
gcc -o sigaction.out sigaction.c
./sigaction.out

sigaction

3. 运行开发板

准备好虚拟机和ubuntu系统,本实验版本为ubuntu18.04x64
参考文档:https://www.csdn.net/tags/OtTaYgwsODIwOTAtYmxvZwO0O0OO0O0O.html

下载、安装、运行QEMU

下载

  1. 下载成功后,进入ubuntu-18.04_imx6ul_qemu_system目录,执行install_sdl.sh,如下所示:
  • 在Ubuntu 18.04系统中,执行如下命令,可以得到一个目录ubuntu-18.04_imx6ul_qemu_system
$ git  clone  https://e.coding.net/weidongshan/ubuntu-18.04_imx6ul_qemu_system.git
  1. 安装SDL
  • 下载成功后,进入ubuntu-18.04_imx6ul_qemu_system目录,执行install_sdl.sh如下所示:
$ cd /home/suliu/Desktop/ubuntu-18.04_imx6ul_qemu_system/
$ ls
imx6ull-system-image  qemu-imx6ull-gui_test.sh  source
install_sdl.sh        qemu-imx6ull-nogui.sh     ubuntu-18.04_sdl-package
qemu                  README.md
qemu-imx6ull-gui.sh   rootfs_test
$ install_sdl.sh

运行QEMU

  1. 必须在Ubunut的桌面环境下启动终端,执行./qemu-imx6ull-gui.sh如下所示:
$ cd /home/suliu/Desktop/ubuntu-18.04_imx6ul_qemu_system/
$ ls
imx6ull-system-image  qemu-imx6ull-gui_test.sh  source
install_sdl.sh        qemu-imx6ull-nogui.sh     ubuntu-18.04_sdl-package
qemu                  README.md
qemu-imx6ull-gui.sh   rootfs_test
$ ./qemu-imx6ull-gui.sh
  • 报错(BUG)
x6ull-gui.sh 
qemu-system-arm: warning: nic imx.enet.0 has no peer
qemu-system-arm: warning: nic imx.enet.1 has no peer
libGL error: MESA-LOADER: failed to open vmwgfx (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri)
libGL error: failed to load driver: vmwgfx
libGL error: MESA-LOADER: failed to open vmwgfx (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri)
libGL error: failed to load driver: vmwgfx
libGL error: MESA-LOADER: failed to open swrast (search paths /usr/lib/x86_64-linux-gnu/dri:\$${ORIGIN}/dri:/usr/lib/dri)
libGL error: failed to load driver: swrast
X Error of failed request:  GLXBadContext
  Major opcode of failed request:  153 (GLX)
  Minor opcode of failed request:  6 (X_GLXIsDirect)
  Serial number of failed request:  114
  Current serial number in output stream:  113
  • 解决(SOLVE):这一步非常关键,因为韦东山老师的文档里没有写,需要执行以下命令安装驱动
$ sudo apt --fix-broken install
  • 重新执行
./qemu-imx6ull-gui.sh

run-sccess

下载、编译内核

参考:http://wiki.100ask.org/Qemu

安装 lzop 工具,执行如下指令

// 先强制安装一遍依赖
$ sudo apt-get install lzop                    // 再安装软件

下载源码

$ git clone https://e.coding.net/codebug8/repo.git
$ mkdir -p 100ask_imx6ull-qemu && cd 100ask_imx6ull-qemu
$ ../repo/repo init -u https://e.coding.net/weidongshan/manifests.git -b linux-sdk -m  imx6ull/100ask-imx6ull_qemu_release_v1.0.xml --no-repo-verify
  • 下载成功后,可以得到如下内容
$ cd 100ask_imx6ull-qemu/
$ ls 
buildroot2019.02  linux-4.9.88  qemu  ToolChain

设置工具链

  1. 执行如下命令:
$ export ARCH=arm
$ export CROSS_COMPILE=arm-linux-gnueabihf-
$ export PATH=$PATH:/home/book/100ask_imx6ull-qemu/ToolChain/gcc-linaro-6.2.1-2016.11-x86_64_arm-linux-gnueabihf/bin

配置、编译内核

  1. 安装交叉编译器(这一步很重要,否则无法编译)
$ sudo apt install gcc-arm-linux-gnueabihf        // 安装编译器。
  1. 执行如下命令:
$ cd linux-4.9.88
$ make mrproper
$ make 100ask_imx6ull_qemu_defconfig
$ make zImage     //这一步需要等待一段时间(10mins)

在QEMU中使用新的zImage

  • 把编译出来的zImage复制到QEMU目录ubuntu-18.04_imx6ul_qemu_system/imx6ull-system-image即可:
$ cd ~/ubuntu-18.04_imx6ul_qemu_system/

替换LCD驱动程序

QEMU所用的内核里已经带有LCD驱动程序了,要测试我们编写的驱动程序,需要做2件事

  1. lcd_drv.c放到内核目录linux-4.9.88/drivers/video/fbdev

  2. 修改linux-4.9.88/drivers/video/fbdev/Makefile如下:

#obj-y += 100ask_qemu_fb.o
obj-y += lcd_drv.o
  1. 最后,即可重新执行make zImage编译内核,内核里就含有新的驱动程序了。

测试

使用新内核启动QEMU后,执行fb-test及可测试

run_lcd

引用

  • 10
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值