linux中线程基本原理和操作原语(操作函数)

我在前面的文章中提到了linux产生进程的三个系统调用:

1.fork() 用来产生拥有独立的虚拟地址空间的子进程,遵循 读时共享写时拷贝 原则
2.vfork() 产生的子进程与父进程共享数据,读写共享,通常搭配exec()或者exit(),否则会死锁
3.clone() linux下用来产生线程的系统调用,我们使用的pthread_create()调用的就是它

线程的基础内容在这篇文章中有提到:
https://blog.csdn.net/KingOfMyHeart/article/details/90261251

一、线程的基础补充内容:加深理解
1.linux下的线程本质还是进程,拥有自己的PCB,只不过地址空间共享;

(可以说是线程是"合租",进程是"独居",主线程也就是一开始的进程的地址空间即为"合租房")
linux上加入线程感觉是比较牵强的,并不是我们想象中的那样.emmmm

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

在没有引入线程的概念时,进程的作用是统资源分配,CPU最小调度单位
而引入线程后,线程继承了“调度单位”,从此以后,进程依旧是资源分配单位,线程成了最小调度单位

二、线程的内核实现原理:线程 LWP
1.从内核的角度来看,进程和线程是一样的,线程都有自己的PCB(在内核的眼里就伪装成了进程),但是PCB指向的三级页表是相同的;
2.线程的执行 表现在函数调用上,所以我们可以认为线程是寄存器和栈的集合;
在这里插入图片描述

从图上看,尽管一个进程中的线程三级页表一样,但是每个线程执行的函数不一样,
这样就足够了,绝对不会发生内存冲突问题。

所以,可以认为线程与线程之间的区别最大的是 每个线程对应的函数栈空间不一样

实际上,内存映射表保存着内核空间,最终是由MMU将虚拟内存映射到真是的物理内存上

在这里插入图片描述
3.进程蜕变成线程:

当一个进程主动的调用pthread_create()时,这个进程我们一般称为主线程

有“独居” 变为 “合租”

4.操作系统区分线程使用的是LWP 线程号:

查看一个进程开启的所有线程:
ps -aux 找到该进程进程号
ps -Lf 进程号     即可得到该进程开启的所有的线程号

注意:这里是线程号,而不是线程id
线程号:cpu用来分配时间片用的
线程id:进程内部区分线程

5.线程之间共享的资源和不共享的资源是哪些:

共享的资源包括:
1.文件描述符表
2.每种信号的处理方式(捕捉、忽略、默认都是共享的)
3.当前的工作目录 和 用户ID 组ID
4.内存地址空间(text/data/heap/bss/共享库)

不共享资源包括:
1.线程ID
2.处理器现场和栈指针(内核栈)
3.独立的栈空间(用户栈)
4.errno变量:     虽然是全局变量,但是依然是私有的
5.信号屏蔽字
6.调度优先级

6.线程的优缺点:

优点:并发性提高、开销小(内核工作量小)、数据通信和共享数据方便

缺点:库函数不稳定(库函数不如系统调用稳定高)、调试困难、对信号支持不好

7.内核创建线程函数原型:

asmlinkage int sys_fork(struct pt_regs *regs)
{
#ifdef CONFIG_MMU
	return do_fork(SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
#else
	/* can not support in nommu mode */
	return(-EINVAL);
#endif
}

asmlinkage int sys_vfork(struct pt_regs *regs)
{
	return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs->ARM_sp, regs, 0, NULL, NULL);
}




//以上函数是创建进程用的
//对照产生进程的函数来看,实际上调用的都是do_fork(),根据传递的参数不同,产生的进程不同

/* Clone a task - this clones the calling program thread.
 * This is called indirectly via a small wrapper
 */
asmlinkage int sys_clone(unsigned long clone_flags, unsigned long newsp,
			 int __user *parent_tidptr, int tls_val,
			 int __user *child_tidptr, struct pt_regs *regs)
{
	if (!newsp)
		newsp = regs->ARM_sp;

	return do_fork(clone_flags, newsp, regs, 0, parent_tidptr, child_tidptr);
}


三、线程的控制原语:

头文件#include<pthread.h>
编译链接使用 -lpthread

1.pthread_self

函数原型: pthread_t pthread_self(void);
作用:获取线程的ID
对应关系:在进程中的函数是  getpid()
返回值:成功返回0,失败不返回

线程ID是进程内部的标识标志,(两个进程之间,线程的ID可以相同)

2.pthread_create

函数原型: int  pthread_create(pthread_t *thread,        //传出参数,产生新线程的ID
							const pthread_attr_*attr    //线程属性,一般为NULL
							void*(start_routine)(void*) 
							//线程的工作函数,是一个函数指针,当该函数运行结束,表示这个线程结束
							void*arg                    //线程的工作函数的参数 );
作用:创建一个新线程
对应关系:在进程中的函数是  fork()
返回值:成功返回0,失败返回错误编号

3.pthread_exit

函数原型:void pthread_exit(void *retval); retval表示线程退出状态,一般为NULL
作用:创建单个线程退出
对应关系:在进程中的函数是  exit() 退出整个进程
返回值:成功返回0,失败返回错误编号

我们可以将主线程中调用pthread_exit()单独的退出,派生线程不受影响

主函数main中:return 和 exit都是 将整个进程退出,return是返回,主函数中返回自然是退出进程了

如果我们在线程的工作函数中加上exit:依然是将整个进程退出

exit()是将进程退出  pthread_exit()是将线程退出 return 是将函数返回
所以我们在多线程中谨慎使用exit()和return,如果要退出线程,使用pthread_exit()即可

4.pthread_join

函数原型:int pthread_join(pthread_t thread,void **retval);
 thread表示线程ID,retval存储的是线程结束状态,一般是线程函数return时的返回值
 
作用:主线程阻塞的等待子线程的退出,获取退出码
对应关系:在进程中的函数是  waitpid()
返回值:成功返回0,失败返回错误编号

5.pthread_detach


线程的分离状态:子线程主动与主线程断开关系,线程结束后,其状态不由其他线程获取,而直接自己释放。网络、多线程服务器常用。
函数原型:int pthread_detach(pthread_t thread);
 thread表示线程ID
 
作用:从状态上分离出来,并不会独占地址空间,仅仅是状态上。
	好处是结束后不会产生僵尸线程,结束自己将自己的PCB从内存中清理掉,父线程不用等待改类型子线程。
对应关系:没有
返回值:成功返回0,失败返回错误编号

6.pthread_cancel

函数原型:int pthread_cancel(pthread_t thread);
 thread表示线程ID
 
作用:将线程杀死
对应关系:对应进程的操作原语是 kill()
返回值:成功返回0,失败返回错误编号
  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux,可以使用信号量和PV原语操作机制实现进程的同步和互斥。 1. 同步机制:可以使用信号量来实现进程的同步。信号量是一种计数器,它用来控制多个进程对共享资源的访问。在Linux,信号量由semget、semop和semctl三个系统调用来完成。 2. 互斥机制:可以使用PV原语操作机制来实现进程的互斥。PV原语操作机制可以实现原子操作,保证多个进程对共享资源的访问是互斥的。在Linux,PV原语操作机制由semaphore.h头文件的sem_init、sem_wait和sem_post三个函数来实现。 下面是一个简单的示例代码: ``` #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> sem_t sem; // 定义信号量 void *thread_func(void *arg) { sem_wait(&sem); // 等待信号量 printf("Thread %d is running\n", *(int *)arg); sem_post(&sem); // 发送信号量 return NULL; } int main() { sem_init(&sem, 0, 1); // 初始化信号量 pthread_t tid[5]; int i; for (i = 0; i < 5; i++) { int *arg = malloc(sizeof(int)); *arg = i; pthread_create(&tid[i], NULL, thread_func, arg); // 创建线程 } for (i = 0; i < 5; i++) { pthread_join(tid[i], NULL); // 等待线程结束 } sem_destroy(&sem); // 销毁信号量 return 0; } ``` 在上面的示例代码,我们使用了sem_wait和sem_post函数来实现线程的同步。在每个线程,我们使用sem_wait函数等待信号量,当信号量的值为1时,线程可以继续执行。在线程执行完后,我们使用sem_post函数发送信号量,将信号量的值加1,以便其他线程可以继续执行。同时,我们使用sem_init函数初始化信号量,使用sem_destroy函数销毁信号量。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值