多线程

多线程,第1张

多线程 认识线程

线程(thread)是 *** 作系统能够进行运算调度的最小单位。


它被包含在进程之中,是进程中的实际运作单位。


安装

Ubuntu默认是没有pthread库的,需要安装

$ sudo apt-get install -y glibc-doc manpages-posix-dev
测试代码实例演示 线程的创建

函数原型:

#include 
int pthread_create(
                 pthread_t *restrict tidp,   //新创建的线程ID指向的内存单元。


const pthread_attr_t *restrict attr, //线程属性,默认为NULL void *(*start_rtn)(void *), //新创建的线程从start_rtn函数的地址开始运行 void *restrict arg //默认为NULL。


若上述函数需要参数,将参数放入结构中并将地址作为arg传入。


);

运行程序:

这里使用pthread_create创建了五个进程并显示出来,之后又用pthread_exit来终止线程。


线程的终止

函数原型:

void pthread_exit(void*_retval)
线程的连接

函数原型:

int pthread_join(
               pthread_t tid, //需要等待的线程,指定的线程必须位于当前的进程中,而且不得是分离线程
               void **status  //线程tid所执行的函数返回值(返回值地址需要保证有效),其中status可以为NULL
                 );

运行:

线程管理

线程的初始化:

int pthread_attr_init(pthread_attr_t *attr);

线程的销毁:

int pthread_attr_destroy(pthread_attr_t *attr);

互斥量、互斥锁

使用互斥量前要先初始化,也就是上面线程管理的内容。


#include 

int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);

对互斥量进行加锁和解锁的函数:

int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

运行:

这里使用互斥锁让进程可以使同一时间只有一个线程访问数据。


死锁

这里通过让两个进程不断争夺运行资源来使程序进入死锁状态。


运行:

因为进入了死锁状态,所以不能继续 *** 作了,这里使用了ctrl+z退出死锁状态。


条件变量

条件变量相关的函数或者 *** 作:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER /*静态初始化cond */
pthread_cond_init(pthread_cond_t xcond,pthread_condattr_t
xcond_attr); /x动态态初始化 / /条件等待,一直等待/ int
pthread_cond_uait(pthread_cond_t cond,pthread_mutex_t kmutex);
/大条件等待,等待一段时间
/ int pthread_cond_timewait(pthread_cond_t
xcond,pthread_mutex kmutex,const timespec
abstime);
/发送唤醒到一个条件变量队列等待的线程/ int pthread_cond_signal(pthread_cond_t xcond);
/发送唤醒到所有在条件变量队列等待的线程/ int pthread_cond_broadcast(pthread_cond_t
xcond); int pthread_cond_destroy(pthread_cond_t xcond); /销毁条件变量/
static pthread_cond_t g_tConVar = PTHREAD_COND_INITIALIZE static
pthread_mutex_t g_tMutex = PTHREAD_MUTEX_INITIALIZER; 线程A:等待条件成立
pthread_mutex_lock(&g_tMutex); pthread_cond_wait(&g_tConVar,
&g_tMutex);. do_something( ) / *** 作临界资源/
pthread_mutex_unlock(&g_tMutex); 线程B:唤醒等待g_tConVar的线程
pthread_cond_signal(&g_tConVar);

运行:

这里不断读取120次满足条件后才会使线程运行。


进程 认识进程

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。



广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。


它是 *** 作系统动态执行的基本单元,在传统的 *** 作系统中,进程既是基本的分配单元,也是基本的执行单元。


2.进程的状态
Linux是一个多用户多任务的 *** 作系统,可以同时运行多个用户的多个程序,就必然会产生多进程,而每个进程都会有不同的状态。



Linux 的进程有以下6种状态:
D:不可中断的深度睡眠状态,处于这种状态的进程不能响应异步信号;
R:进程处于运行态或就绪状态,只有在该状态的进程才可能在CPU上运行。


而同一时刻可能有多个进程处于可执行状态;
S:可中断的睡眠状态,处于这个状态的进程因为等待某种事件的发生而被挂起。


;
T:暂停状态或跟踪状态;
X:退出状态,进程即将被销毁;
Z:退出状态,进程成为僵尸进程。


测试代码实例演示 获取环境变量
  • 进程的参数

    • 环境变量
      • 进程获取环境变量的3种途径:

        • 1)通过main()函数的第3个参数env获取;
          Main ()函数的三种原型:
          int main();
          int main(int argc, char *argvO);
          int main(int argc, char *argv[], char *env);
          argc表明命令行参数的个数;argv是指向参数的各个指针所构成的数组, env参数是指向环境变量字符串的数组

        • 2)通过environ全局变量获取;

        • 3)通过getenv()函数获取。


创建进程
  • 函数以拷贝父进程的方式创建子进程。


    子进程与父进程有相同的代码空间、文件描述符等资源

  • 创建后,子进程与父进程开始并发执行,执行顺序由内核调度算法来决定
  • fork()对父子进程各返回一次,
    父进程:子进程的PID,
    子进程:0;
    失败: 小于0;


运行:


这里就打印出来了父进程和子进程的pid。


使用进程创建新程序


运行:

使用子进程加载新程序

wait()函数

原型:

pid_t wait(int kstatus)
  • status是用来保存子进程退出状态的指针·帮助父进程获取其子进程的退出状态
  • 如果父进程未调用wait()函数,则子进程的退出信息将一直保存在内存中。


    • 僵尸进程:子进程先终止,但父进程没有调用wait()。


      一直占用系统资源。


    • 孤儿进程:被init进程收管,由init进程负责收集它们的退出状态

运行:

这里创建了两个子进程,一个正常推出返回规定值,一个用abort()函数退出,返回了退出状态的信息。


守护进程
  • 后台运行的一种特殊进程,独立于控制终端,周期性地执行某种任务或等待处理某些事件守护进程属于孤儿进程,父进程变为init进程。


  • Linux系统的大多数服务器就是通过守护进程实现,通常以字母d结尾来命名进程名,如sshd、xinetd、 crond等。


  • 常用daemon()创建守护进程。


    • 函数原型:
int daemon(int nochdir, int noclose);

运行:

信号 sigaction函数

函数结构:

struct sigaction {
    void (*sa_handler)(int);
    void (*sa_sigaction)(int, siginfo_t *, void *);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
	}

sa_handler此参数和signal()的参数handler相同,代表新的信号处理函数
sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号集搁置
sa_flags 用来设置信号处理的其他相关 *** 作,下列的数值可用。



SA_RESETHAND:当调用信号处理函数时,将信号的处理函数重置为缺省值SIG_DFL
SA_RESTART:如果信号中断了进程的某个系统调用,则系统自动启动该系统调用
SA_NODEFER :一般情况下, 当信号处理函数运行时,内核将阻塞该给定信号。


但是如果设置了 SA_NODEFER标记, 那么在该信号处理函数运行时,内核将不会阻塞该信号


运行:

这样进程一直在循环等待信号的发生,所以只好用ctrl+z来退出。


或者我们可以使用ctrl+c给它一个信号:


程序代码利用sigaction函数实现当进程首次收到SIGINT(Ctrl+c)信号时在终端打印Ouch信息,第2次按Ctrl+c时进程退出。




子进程等待、退出以及输出信号

运行:


这里进程在经过两次等待后,父进程将信号传给子进程,之后通过exit()退出,并输出子进程的退出状态。


可以用kill-l查看所有信号:

管道

进程间通信(通信6法)

  • 管道(Pipe) 及命名管道(named pipe) :管道用于有血缘亲缘关系进程间的通信, 命名名管道允许无血缘无亲缘关系进程间的通信。


  • 共享内存(Shared memory) :可以说这是最有用的进程间通信方式。


    它使得多 个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中
    数据的更新。


    这种通信方式需要依靠某种同步机制,如互斥锁和旗语信号量等。


  • 消息队列(Messge Queue) :消息队列是消息的链接表,包括Posix消息队列 SystemV消息队列。


    它克服了管道、内存通信信息量有限的缺点,具有写权限的进
    程可以按照一定的规则向消息队列中添加新消息;对消息队列有读权限的进程则可 以从消息队列中读取消息。


  • 信号(Signal):软件层次上对中断机制的一种模拟,进程收到信号类似处理器收 到中断请求。


    用于通知进程有某事件发生,是较复杂的通信方式。


  • 旗语信号量(Semaphore) : (旗语使用两种旗子) 主要作为进程之间以及同一-进程的不 同线程之间的同步和互斥手段,分为匿名内存信号量和命名信号量。


  • 套接字(Socket) :这是一种更为一般的进程间通信机制,它可用于网络中不同机 器之间的进程间通信,应用非常广泛。


  • 管道,即进程间数据流的通道。


    通常A进程输出连B进程输入端。



    符号“|"

    • 匿名管道: pipe文件

函数原型:

int pipe(int pipefd[2])
  • 命名管道: fifo文件
    真实创建一个文件(“命名管道文件”,特殊的文件,标记为p)

函数原型:

 int mkfifo(const char *pathname, mode_t mode) ;

参数pathname指定了文件名
参数mode则指定了文件的读写权限。


示例:
$ ps auxf | grep init

通过命名管道发送文件

通过命名管道保存文件

共享内存 Wirte

Read

其他使用例

开发板

打开开发板:

$  ~/ubuntu-18.04_imx6ul_qemu_system/gui-qemu-imx6ull-gui.sh

等待它配置完成

打开LCD图像和屏幕:
$ fb-test
$ cd myfb-test

$ ./myfb-test /dev/fb0

串口EEPROM
$ cd
$ i2cdetect -l #列出所有ic2总线
$ i2cdetect -y 0 #列出总线0上的设备

UU:有设备,有内核驱动
地址:有设备,无驱动

这里要选择是否继续,是因为这样列出可能会使数据损坏,但这里是实验,所以就选择是。


在显示出来的矩阵中,最左边一列是十位,最上面一行是个位。


由于那些没有数字的现在是没有驱动的,但我们可以使用i2c_usr_test来绕过驱动。


对总线进行写入:

$ i2c_usr_test /dev/i2c-0 0x50 w 0x01 0xff     #w = write

解释:
/dev/i2c-0 是这个总线的设备符,0x50是被 *** 作的地址,w表示对地址进行写,0x01是位置,0xff是我们想写入的内容。


这里我又在位置33追加输入了dd。


读取:

$ i2c_usr_test /dev/i2c-0 0x50 w 0x33 

因为只是读取,所以就不需要后面的内容了,这里我们读取位置33的内容。


当我们写入的时候,在仿真图上是红色,而当读的时候是绿色的。


命令控制LED

打开LED控制、安装LED驱动:

$ cd
$ cd led_driver_qemu/
$ insmod 100ask_led.ko

控制LED零号灯亮,控制LED灯一号灯灭:

$ ./ledtest /dev/100ask_led0 on
$ ./ledtest /dev/100ask_led1 off

按键控制LED

安装驱动:

$ cd
$ cd button_driver_qemu/
$ insmod button_drv.ko #扫描你的按键
$ insmod board_100ask_qemu_imx6ull.ko 

启动按键控制:

$ ./button_led_test

之后你就可以通过虚拟的按键来控制LED灯的亮灭了。



退出就直接按ctrl+z。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/584909.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-12
下一篇 2022-04-12

发表评论

登录后才能评论

评论列表(0条)

保存