linux 下的进程创建线程,Linux线程 之 线程 线程组 进程 轻量级进程(LWP)
线程是有栈的,我们知道,普通的一个进程,它的栈空间是8M,我们可以通过ulmit -a查看:
stack size (kbytes, -s) 8192
线程也不例外,线程也是需要栈空间的这句话是废话,呵呵。对于属于同一个进程(或者说是线程组)的多个线程他们是共享一份虚拟内存地址的,如下图所示。这也就决定了,你不能无限制创建线,因为纵然你什么都不做,每个线程默认耗费8M的空间(事实上还不止,还有管理结构,后面陈述)。Ulrich Drepper大神有篇文章《Thread numbers and stacks》,分析了线程栈空间方面的计算。如果我们真的需要很多个线程的话,幸好我们还是可以做一些事情。我们可以通过pthread_attr_setstacksize,设定好stack size属性然后在pthread_create.
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
线程栈如上图所示,共享进程(或者称之为线程组)的虚拟地址空间。既然多个线程聚集在一起,我怎么知道我要操作的那个线程栈的地址呢。要解决这个问题,必须要领会线程和进程以及线程组的概念。我不想写一堆片汤话,下面我运行我的测试程序,然后结合现象分析原因:
#include
#include
#include
#include
#define gettid() syscall(__NR_gettid)
pthread_key_t key;
__thread int count = 2222;
__thread unsigned long long count2 ;
static __thread int count3;
void echomsg(int t)
{
printf("destructor excuted in thread %x,param=%x\n",pthread_self(),t);
}
void * child1(void *arg)
{
int b;
int tid=pthread_self();
printf("I am the child1 pthread_self return %p gettid return %d\n",tid,gettid());
char* key_content = malloc(8);
if(key_content != NULL)
{
strcpy(key_content,"ACACACA");
}
pthread_setspecific(key,(void *)key_content);
count=666666;
count2=1023;
count3=2048;
printf("I am child1 , tid=%x ,count (%p) = %10d,count2(%p) = %10llu,count3(%p) = %6d\n",tid,&count,count,&count2,count2,&count3,count3);
asm volatile("movl %%gs:0, %0;"
:"=r"(b) /* output */
);
printf("I am child1 , GS address %x\n",b);
sleep(2);
printf("thread %x returns %x\n",tid,pthread_getspecific(key));
sleep(50);
}
void * child2(void *arg)
{
int b;
int tid=pthread_self();
printf("I am the child2 pthread_self return %p gettid return %d\n",tid,gettid());
char* key_content = malloc(8);
if(key_content != NULL)
{
strcpy(key_content,"ABCDEFG");
}
pthread_setspecific(key,(void *)key_content);
count=88888888;
count2=1024;
count3=2047;
printf("I am child2 , tid=%x ,count (%p) = %10d,count2(%p) = %10llu,count3(%p) = %6d\n",tid,&count,count,&count2,count2,&count3,count3);
asm volatile("movl %%gs:0, %0;"
:"=r"(b) /* output */
);
printf("I am child2 , GS address %x\n",b);
sleep(1);
printf("thread %x returns %x\n",tid,pthread_getspecific(key));
sleep(50);
}
int main(void)
{
int b;
pthread_t tid1,tid2;
printf("hello\n");
pthread_key_create(&key,echomsg);
asm volatile("movl %%gs:0, %0;"
:"=r"(b) /* output */
);
printf("I am the main , GS address %x\n",b);
pthread_create(&tid1,NULL,child1,NULL);
pthread_create(&tid2,NULL,child2,NULL);
printf("pthread_create tid1 = %p\n",tid1);
printf("pthread_create tid2 = %p\n",tid2);
sleep(60);
pthread_key_delete(key);
printf("main thread exit\n");
return 0;
}
这是一个比较综合的程序,因为我下面要多次从不同的侧面分析。对于现在,我们要展示的是进程 线程 线程组的关系。在一个终端运行编译出来的test2程序,显示的信息如下:
另一个终端看ps信息,ps显示的信息如下:
直接ps,是看不到我们创建的线程的。只有3658一个进程。当我们采用ps -eLf的时候,我们看到了三个线程3658/3659/3660,或者称之为轻量级进程(LWP)。Linux到底是怎么看待这三者的关系的呢:
Linux下多线程程序,一般都是有一个主进程通过调用pthread_create创建了一个或者多个子线程,如同我们的程序,主进程在main中创建了两个子进程。那么Linux到底是怎么看待这些事情的呢?
pid_t pid;
pid_t tgid;
...
struct task_struct *group_leader; /* threadgroup leader */
上面三个变量是进程描述符的三个成员变量。pid字面意思是process id,其实叫thread id会更合适。tgid 字面含义是thread group ID。对于存在多个线程的程序而言,每个线程都有自己的pid,没错pid,如同我们例子中的3658/3659/3660,但是都有个共同的线程组ID (TGID):3658 。
好吧,我们再重新说一遍,对于普通进程而言,我们可以称之为只有一个LWP的线程组,pid是它自己的pid,tgid还是它自己,线程组里面只有他自己一个光杆司令,自然group_leader也是它自己。但是多线程的进程(线程组更恰当)则不然。开天辟地的main函数所在的进程会有自己的PID,也会有也TGID,group_leader,都是他自己。注意,它自己也是LWP。后面他使用ptherad_create创建了2个线程,或者LWP,这两个新创建的线程会有自己的PID,但是TGID会沿用创建自己的那个进程的TGID,group_leader也会尊创建自己的进程的进程描述符(task_struct)为自己的group_leader。copy_process函数中有如下代码:
p->pid = pid_nr(pid);
p->tgid = p->pid;//普通进程
if (clone_flags & CLONE_THREAD)
p->tgid = current->tgid;//线程选择叫起它的进程的tgid作为自己的tgid
....
p->group_leader = p;//普通进程
INIT_LIST_HEAD(&p->thread_group);
...
if (clone_flags & CLONE_THREAD) {
current->signal->nr_threads++;
atomic_inc(¤t->signal->live);
atomic_inc(¤t->signal->sigcnt);
p->group_leader = current->group_leader;//线程选择叫起它的进程作为它的group_leader
list_add_tail_rcu(&p->thread_group, &p->group_leader->thread_group);
}
OK,ps -eLf中有个字段叫NLWP,就是线程组中LWP的个数,对于我们的例子,main函数所在LWP+两个线程 = 3.
我们传说的getpid函数,本质取得是进程描述符的TGID,而gettid系统调用,取得才是每个LWP各自的PID。请看上面的图片输出,上面连个线程gettid返回的是3873和3874,是自己的PID。稍微有点毁三观
除此外,需要指出的是用户态pthread_create出来的线程,在内核态,也拥有自己的进程描述符task_struct(copy_process里面调用dup_task_struct创建)。这是什么意思呢。意思是我们用户态所说的线程,一样是内核进程调度的实体。进程调度,严格意义上说应该叫LWP调度,进程调度,不是以前面提到的线程组为单位调度的,本质是以LWP为单位调度的。这个结论乍一看惊世骇俗,细细一想,其是很合理。我们为什么多线程?因为多CPU,多核,我们要充分利用多核,同一个线程组的不同LWP是可以同时跑在不同的CPU之上的,因为这个并发,所以我们有线程锁的设计,这从侧面证明了,LWP是调度的实体。
我们用systemtap去观察下test2程序相关的调度:systemtap脚本如下:
linux 下的进程创建线程,Linux线程 之 线程 线程组 进程 轻量级进程(LWP)相关推荐
- Linux下的LVM创建以及Linux快照卷
通过最的学习,我加深了对LVM的理解,在这里,我将它写成博客,一是方便自己记忆,二是让Linux的入门学习者一起学习一下. 首先我们引入LVM的概念,并展开详细的说明,主要是近期学习的看法,我们先看一 ...
- 《LINUX下动态链接库的创建与应用》
大家都知道,在windows系统中有很多的动态链接库(以.dll为后缀的文档,dll即dynamic link library).这种动态链接库,和静态函数库不同,他里面的函数并不是执行程式本身的一部 ...
- linux 查看核数与线程数,Linux下查看系统CPU个数、核心数、线程数
Linux下查看系统CPU个数.核心数.线程数 查看系统CPU个数(物理个数)cat /proc/cpuinfo |grep "physical id"|sort -u 结果如下: ...
- linux下多线程的创建与等待详解 【转载】
linux下多线程的创建与等待详解 http://blog.chinaunix.net/uid-23842323-id-2656572.html 所有线程都有一个线程号,也就是Thread ID.其类 ...
- linux lvm添加磁盘,Linux下添加磁盘创建lvm分区
shell> fdisk /dev/xvdb #### 选择磁盘 Command (m for help): m #### 帮助 Command action a toggle a bootab ...
- linux cvs账户,在linux下为cvs创建用户
在linux下为cvs创建用户 1.创建可以登陆cvs服务器的用户名和密码: #> su cvsroot #> vi /home/cvsroot/CVSROOT/passwd test1: ...
- linux下使用mdadm组软raid,Linux下使用mdadm创建和管理软raid
Linux下使用mdadm创建和管理软raid 注:本次操作以RHEL4为例,但应该可以应用到其它大部分的distro上(guess). mdadm的几个常用参数 -C 创建Raid,后面跟参数,代表 ...
- linux下剪切文件怎么恢复,linux下文件夹的创建、复制、剪切、重命名、清空和删除命令...
在home目录下有wwwroot目录,wwwroot下有sinozzz目录,即/home/wwwroot/sinozzz 一.目录创建 在/home/wwwroot目录下新建一个sinozzz123的 ...
- linux新建图片,Fedora 下的图像创建程序 | Linux 中国
原标题:Fedora 下的图像创建程序 | Linux 中国 Fedora 有很多程序可以帮助你的创造力.从数字绘图.矢量到像素艺术,每个人都可以在这个周末发挥创意. -- Ryan Lerch 致谢 ...
- linux软RAId配置与管理总结,Linux下使用mdadm创建和管理软raid(转)
Linux下使用mdadm创建和管理软raid 注:本次操作以RHEL4为例,但应该可以应用到其它大部分的distro上(guess). mdadm的几个常用参数 -C 创建Raid,后面跟参数,代表 ...
最新文章
- Telent 远程登录服务
- 解决:此错误(HTTP 500 内部服务器错误)意味着您正在访问的网站出现了服务器问题,此问题阻止了该网页的显示...
- 关于JavaScript并发、竞态场景下的一些思考和解决方案
- 修复迁移后Net Standard项目中的错误
- 文字描边_如何在网页里实现文字描边效果
- java 二叉树迭代器_C,为二叉树实现自定义迭代器(长)
- java编写一个程序_计算已知长和宽的长方形的周长,请教一下大佬们,我们java留了一个作业,编写程序,定义一个接口Comput,声明计算周长和面积的方法...
- mysql Split函数
- leetcode python3 简单题119. Pascal's Triangle II
- [转]Android SurfaceView 绘图覆盖刷新及脏矩形刷新方法
- 1137. 第 N 个泰波那契数 动态规划
- 计算机408考研笔记汇总
- 美国国家安全局硬盘固件入侵技术揭秘
- H264、H265编码概念及I帧P帧B帧
- 惠普扫描应用程序当前正由此计算机上的其,惠普打印机 打印扫描的时候显示“由于另一个程序或另一台计算机正在使用联网的HP成像设备因此无法执行”...
- 常用 javascript广告代码
- 电路中的正弦信号 Sin
- 带你玩转以太坊智能合约的Hello World
- 王牌英雄怎么服务器维护了,王牌英雄新手入门指南 王牌英雄玩法技巧
- Python:tkinter简易广告牌
热门文章
- 安卓和iOS的兼容性问题: 键盘弹起时,固定在底部的按钮是否被弹到键盘上方
- WIFI:802.11协议帧格式
- 根本原因分析(RCA)
- 大禹电子:超声波小型水声通信机可用于水下实时定位系统
- Python爬虫系列:腾讯课堂Scrapy爬虫
- 一米村长讲故事机器人_又一位主持人跑去给孩子讲故事,村长李锐推出有声故事品牌“村长讲故事”...
- [深度学习]动手学深度学习笔记-15
- Python GIS神器shapely 2.0新版本来了
- 数据标注下半场:PLG模式下的增长飞轮丨曼孚科技
- deepin/ubantu下 mysql 1698错误解决