一、进程组和会话
进程组,也称之为作业。BSD 于 1980 年前后向 Unix 中增加的一个新特性。代表一个或多个进程的集合。每个 进程都属于一个进程组。在 waitpid 函数和 kill 函数的参数中都曾使用到。操作系统设计的进程组的概念,是为了简 化对多个进程的管理。
当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组 ID==第一个进程 ID(组长进程)。 所以,组长进程标识:其进程组 ID==其进程 ID
可以使用 kill -SIGKILL -进程组 ID(负的)来将整个进程组内的进程全部杀死。
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。
二、守护进程:
1.守护进程:
daemon(精灵)进程。通常运行与操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作。
不受用户登录注销影响。通常采用以d结尾的命名方式。
2.守护进程创建步骤:
1. fork子进程,让父进程终止。
2. 子进程调用 setsid() 创建新会话
3. 通常根据需要,改变工作目录位置 chdir(), 防止目录被卸载。
4. 通常根据需要,重设umask文件权限掩码,影响新文件的创建权限。
022 -- 755 0345 --- 432 r---wx-w- 422
5. 通常根据需要,关闭/重定向文件描述符
6. 守护进程业务逻辑。while()
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <pthread.h> #include <fcntl.h> #include <sys/stat.h> void sys_err(const char *str){ perror(str); exit(1); } int main(int argc,char *argv[]) { pid_t pid; int ret,fd; pid = fork(); //创建进程 if(pid > 0) //父进程终止 exit(0); pid = setsid(); //创建新会话 if(pid == -1) sys_err("setsid error"); ret = chdir("/home/book/Desktop"); //改变工作目录位置 if(ret == -1) sys_err("chdir error"); umask(0022); //改变文件访问权限掩码 close(STDIN_FILENO); //关闭文件描述符 0 fd = open("/dev/null",O_RDWR); //fd --> 0 if(fd == -1) sys_err("open error"); dup2(fd,STDOUT_FILENO); //重定向stdout dup2(fd,STDERR_FILENO); //重定向stderr while(1); //模拟守护进程业务 return 0; }
用户的登录注销不影响进程
三、线程
1.三级映射:
三级映射:
进程 PCB --> 页目录(可看成数组,首地址位于 PCB 中) --> 页表 --> 物理页面 --> 内存单元
2.线程概念:
进程:有独立的进程地址空间。有独立的pcb。 分配资源的最小单位。
线程:有独立的pcb。没有独立的进程地址空间。 最小单位的执行。
ps -Lf 进程id ---> 线程号。 LWP ---> cpu 执行的最小单位。
最小执行单位:
3.线程共享:
独享栈空间(内核栈、用户栈)
共享 ./text./data ./rodataa ./bsss heap ---> 共享【全局变量】(errno)
(1)线程共享资源
1).文件描述符表
2).每种信号的处理方式
3).当前工作目录
4).用户 ID 和组 ID
5).内存地址空间 (.text/.data/.bss/heap/共享库)
(2) 线程非共享资源
1).线程 id
2).处理器现场和栈指针(内核栈)
3).独立的栈空间(用户空间栈)
4).errno 变量
5).信号屏蔽字
6).调度优先级
4.线程优、缺点
优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便
缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb 不支持 3. 对信号支持不好
优点相对突出,缺点均不是硬伤。Linux 下由于实现方法导致进程、线程差别不是很大。
5.线程控制原语:
1)获取线程id
线程id是在进程地址空间内部,用来标识线程身份的id号。
pthread_t pthread_self(void);
返回值:本线程id
2) 创建子线程。
int pthread_create(pthread_t tid, const pthread_attr_t attr, void (start_rountn)(void ), void arg);
参1:传出参数,表新创建的子线程 id
参2:线程属性。传NULL表使用默认属性。
参3:子线程回调函数。创建成功,pthread_create函数返回时,该函数会被自动调用。
参4:参3的参数。没有的话,传NULL
返回值:成功:0
失败:errno
获取线程id、创建子线程:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> void sys_err(const char *str) { perror(str); exit(1); } //子线程 void *tfn(void *arg) //子进程回调函数 { printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self());//获取进程id return NULL; } //主线程 int main(int argc, char *argv[]) { pthread_t tid; int ret = pthread_create(&tid, NULL, tfn, NULL); //创建子进程 if (ret != 0) { perror("pthread_create error"); } printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self());//获取进程id //sleep(1); //return 0; pthread_exit((void *)0); }
3)循环创建N个子线程:
for (i = 0; i < 5; i++)
pthread_create(&tid, NULL, tfn, (void )i); // 将 int 类型 i, 强转成 void , 传参。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> void sys_err(const char *str) { perror(str); exit(1); } void *tfn(void *arg) { int i = (int)arg; //强转.受参数类型限制 sleep(i); printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self()); return NULL; } int main(int argc, char *argv[]) { int i; int ret; pthread_t tid; for (i = 0; i < 5; i++) { ret = pthread_create(&tid, NULL, tfn, (void *)i); // i 传参采用 值传递. 借助强转. if (ret != 0) { sys_err("pthread_create error"); } } sleep(i); printf("main: I'm Main, pid = %d, tid= %lu\n", getpid(), pthread_self()); return 0; }
4)线程中全局变量共享
线程默认共享数据段、代码段等地址空间,常用的是全局变量。而进程不共享全局变量,只能借助 mmap
#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h> int var = 100; void *tfn(void *arg) { var = 200; printf("thread, var = %d\n", var); return NULL; } int main(void) { printf("At first var = %d\n", var); pthread_t tid; pthread_create(&tid, NULL, tfn, NULL); sleep(1); printf("after pthread_create, var = %d\n", var); return 0; }
如果遇到以下编译问题,不要慌张
问题的原因:pthread不是linux下的默认的库,也就是在链接的时候,无法找到phread库中哥函数的入口地址,于是链接会失败。
解决:在gcc编译的时候,附加要加 -lpthread参数即可解决。
试用如下命令即可编译通过
gcc z.c -o z -lpthread
5) 退出当前线程
void pthread_exit(void retval);
retval:退出值。 无退出值时,NULL
exit(); 退出当前进程。
return: 返回到调用者那里去。
pthread_exit(): 退出当前线程。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> void sys_err(const char *str) { perror(str); exit(1); } void *tfn(void *arg) { int i = (int)arg; //强转.受参数类型限制 sleep(i); if(i == 2){ } printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self()); return NULL; } int main(int argc, char *argv[]) { int i; int ret; pthread_t tid; for (i = 0; i < 5; i++) { ret = pthread_create(&tid, NULL, tfn, (void *)i); // i 传参采用 值传递. 借助强转. if (ret != 0) { sys_err("pthread_create error"); } } sleep(i); printf("main: I'm Main, pid = %d, tid= %lu\n", getpid(), pthread_self()); return 0; }
exit(0) 退出线程
/********使用exit(0)**************/ void *tfn(void *arg) { int i = (int)arg; sleep(i); if(i == 2){ exit(0); //表示退出线程 } printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self()); return NULL; }
return NULL 返回到调用者那里
/**********使用return NULL**************/ void *tfn(void *arg) { int i = (int)arg; sleep(i); if(i == 2){ return NULL; //返回到调用者那里 } printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self()); return NULL; }
pthread_exit(NULL) 当前线程退出
/*****使用pthread_exit(NULL) 当前线程退出********/ void func(void) { pthread_exit(NULL); return; } void *tfn(void *arg) { int i = (int)arg; //强转.受参数类型限制 sleep(i); if(i == 2){ func(); //当前线程退出 } printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self()); return NULL; } /*********或者*************/ void *tfn(void *arg) { int i = (int)arg; //强转.受参数类型限制 sleep(i); if(i == 2){ pthread_exit(NULL); //当前线程退出 } printf("--I'm %dth thread: pid = %d, tid= %lu\n", i+1, getpid(), pthread_self()); return NULL; }
6) 阻塞 回收线程
int pthread_join(pthread_t thread, void retval);
thread: 待回收的线程id
retval:传出参数。 回收的那个线程的退出值。
线程异常借助,值为 -1。
返回值:成功:0
失败:errno
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <pthread.h> struct thrd{ int var; char str[256]; }; void sys_err(const char *str) { perror(str); exit(1); } void *tfn(void *arg) { struct thrd *tval; tval = malloc(sizeof(tval)); tval->var = 100; strcpy(tval->str,"hello thread"); return (void *)tval; } int main(int argc,char *argv[]) { pthread_t tid; struct thrd *retval; int ret = pthread_create(&tid,NULL,tfn,NULL); if(ret != 0) sys_err("pthread_create error"); ret = pthread_join(tid,(void **)&retval); if(ret != 0) sys_err("pthread_join error"); printf("child thread exit with var = %d,str = %s\n",retval->var,retval->str); pthread_exit(NULL); return 0; }
#include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <pthread.h> void sys_err(const char *str) { perror(str); exit(1); } void *tfn(void *arg) { return (void *)74; } int main(int argc,char *argv[]) { pthread_t tid; int *retval; int ret = pthread_create(&tid,NULL,tfn,NULL); if(ret != 0) sys_err("pthread_create error"); ret = pthread_join(tid,(void **)&retval); if(ret != 0) sys_err("pthread_join error"); printf("child thread exit with %d\n",(void *)retval); pthread_exit(NULL); return 0; }
注意:
局部变量地址,不可做返回值
void *tfn(void *arg) { struct thrd tval; //局部变量地址,不可做返回值 tval = malloc(sizeof(tval)); tval->var = 100; strcpy(tval->str,"hello thread"); return (void *)tval; }
7)杀死一个线程。
需要到达取消点(保存点)
int pthread_cancel(pthread_t thread);
thread: 待杀死的线程id
返回值:成功:0
失败:errno
如果,子线程没有到达取消点, 那么 pthread_cancel 无效。
我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();
成功被 pthread_cancel() 杀死的线程,返回 -1.使用pthead_join 回收。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> void tfn() { while(1){ printf("thread: pid = %d,tid = %lu\n",getpid(),pthread_self()); sleep(1); } return NULL; } int main(int argc,char *argv[]) { pthread_t tid; int ret = pthread_create(&tid,NULL,tfn,NULL); if(ret != 0){ fprintf(stderr,"pthread_create error:%s\n",strerror(ret)); exit(1); } printf("main: pid =%d,tid = %lu\n",getpid,pthread_self()); sleep(5); ret = pthread_cancel(tid); if(ret != 0){ fprintf(stderr,"pthread_cancel error:%s\n",strerror(ret)); exit(1); } while(1); return 0; }
8)设置线程分离
int pthread_detach(pthread_t thread);
thread: 待分离的线程id
返回值:成功:0
失败:errno
注意:检查出错返回: 线程中:
fprintf(stderr, "xxx error: %s\n", strerror(ret));
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> void *tfn(void *arg) { printf("thread: pid = %d, tid= %lu\n",getpid(), pthread_self()); return NULL; } int main(int argc, char *argv[]) { int ret; pthread_t tid; ret = pthread_create(&tid, NULL, tfn, NULL); // i 传参采用 值传递. 借助强转. if (ret != 0) { fprintf(stderr,"pthread_create error :%s\n",strerror(ret)); exit(1); } ret = pthread_detach(tid); //设置线程分离,线程终止,会自动清理pcb,无需回收 if (ret != 0) { fprintf(stderr,"pthread_detach error :%s\n",strerror(ret)); exit(1); } sleep(2); ret = pthread_join(tid,NULL); //tid为无效值 if (ret != 0) { fprintf(stderr,"pthread_join error :%s\n",strerror(ret)); exit(1); } printf("main: I'm Main, pid = %d, tid= %lu\n", getpid(), pthread_self()); pthread_exit((void *)0); }
9) 线程与进程原语比较
线程控制原语 进程控制原语
pthread_create() fork();
pthread_self() getpid();
pthread_exit() exit(); / return
pthread_join() wait()/waitpid()
pthread_cancel() kill()
pthread_detach()
6.线程属性:
设置分离属性。
pthread_attr_t attr 创建一个线程属性结构体变量
pthread_attr_init(&attr); 初始化线程属性
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
设置线程属性为分离态
pthread_create(&tid, &attr, tfn, NULL); 借助修改后的 设置线程属性 创建为分离态的新线程
pthread_attr_destroy(&attr); 销毁线程属性
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <pthread.h> void *tfn(void *arg) { printf("thread: pid = %d, tid = %lu\n", getpid(), pthread_self()); return NULL; } int main(int argc, char *argv[]) { pthread_t tid; pthread_attr_t attr; int ret = pthread_attr_init(&attr); if (ret != 0) { fprintf(stderr, "attr_init error:%s\n", strerror(ret)); exit(1); } ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 设置线程属性为 分离属性 if (ret != 0) { fprintf(stderr, "attr_setdetachstate error:%s\n", strerror(ret)); exit(1); } ret = pthread_create(&tid, &attr, tfn, NULL); if (ret != 0) { perror("pthread_create error"); } ret = pthread_attr_destroy(&attr); if (ret != 0) { fprintf(stderr, "attr_destroy error:%s\n", strerror(ret)); exit(1); } //sleep(1) //等待子线程死亡 //因为join是可以阻塞的,所以不sleep也可以 ret = pthread_join(tid, NULL); //回收子线程,回收失败线程分离 if (ret != 0) { fprintf(stderr, "pthread_join error:%s\n", strerror(ret)); exit(1); } printf("main: pid = %d, tid = %lu\n", getpid(), pthread_self()); pthread_exit((void *)0); }
7.线程使用注意事项
1). 主线程退出其他线程不退出,主线程应调用 pthread_exit
2). 避免僵尸线程
pthread_join
pthread_detach
pthread_create 指定分离属性
被 join 线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
3). malloc 和 mmap 申请的内存可以被其他线程释放
4). 应避免在多线程模型中调用 fork 除非,马上 exec,子进程中只有调用 fork 的线程存在,其他线程在子进程 中均 pthread_exit
5) . 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制