linux 8day
1.终端
在unix系统中 用户通过终端登录系统得到shell进程,这个终端成为shell进程的控制终端
前台进程于后台进程
tty可以直接获取终端
函数说明:
#include <unistd.h>
char *ttyname(int fd);
功能:由文件描述符查出对应的文件名
参数:
fd:文件描述符
返回值:
成功:终端名
失败:NULL
下面我们借助ttyname函数,通过实验看一下各种不同的终端所对应的设备文件名:
int main()
{
printf("fd 0: %s\n", ttyname(0));
printf("fd 1: %s\n", ttyname(1));
printf("fd 2: %s\n", ttyname(2));
return 0;
}
//都是输出的终端名字
2.进程组 pjid
代表一个或多个进程的集合 更加使得ubt便于管理
当父进程,创建子进程的时候,默认子进程与父进程属于同一进程组。进程组ID为第一个进程ID(组长进程)。所以,组长进程标识:其进程组ID为其进程ID
可以使用kill -SIGKILL -进程组ID(负的)来将整个进程组内的进程全部杀死:
组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。
进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。
一个进程可以为自己或子进程设置进程组ID。
3.进程函数
#include <unistd.h>
pid_t getpgrp(void); /* POSIX.1 version */
功能:获取当前进程的进程组ID
参数:无
返回值:总是返回调用者的进程组ID
pid_t getpgid(pid_t pid);
功能:获取指定进程的进程组ID
参数:
pid:进程号,如果pid = 0,那么该函数作用和getpgrp一样
返回值:
成功:进程组ID
失败:-1
int setpgid(pid_t pid, pid_t pgid);
功能:
改变进程默认所属的进程组。通常可用来加入一个现有的进程组或创建一个新进程组。
参数:
将参1对应的进程,加入参2对应的进程组中
返回值:
成功:0
失败:-1
4.会话
会话是一个或多个进程组的集合。
5.会话的创建
1.会话注意事项
- 调用进程不能是进程组组长,该进程变成新会话首进程(session header)
- 该调用进程是组长进程,则出错返回
- 该进程成为一个新进程组的组长进程
- 需有root权限(ubuntu不需要)
- 创建的会话丢弃原有的控制终端,会话无控制终端
- 建立新会话时,先调用fork, 父进程终止,子进程调用setsid
2.会话api函数
getsid函数:
#include <unistd.h>
pid_t getsid(pid_t pid);
功能:获取进程所属的会话ID
参数:
pid:进程号,pid为0表示查看当前进程session ID
返回值:
成功:返回调用进程的会话ID
失败:-1
组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
setsid函数:
#include <unistd.h>
pid_t setsid(void);
功能:
创建一个会话,并以自己的ID设置进程组ID,同时也是新会话的ID。调用了setsid函数的进程,既是新的会长,也是新的组长。
参数:无
返回值:
成功:返回调用进程的会话ID
失败:-1
6.守护进程
生产周期长,独立于控制终端,周期执行某些任务
Linux 的大多数服务器就是用守护进程实现的。比如,Internet 服务器 inetd,Web 服务器 httpd 等。
1.守护进程模型
- 创建子进程,父进程退出(必须)
-
所有工作在子进程中进行形式上脱离了控制终端
-
2.在子进程中创建新会话(必须)
-
setsid()函数
-
使子进程完全独立出来,脱离控制
3.改变当前目录为根目录(不是必须)
-
chdir()函数
-
防止占用可卸载的文件系统
-
也可以换成其它路径
4.重设文件权限掩码(不是必须)
-
umask()函数
-
防止继承的文件创建屏蔽字拒绝某些权限
-
增加守护进程灵活性
掩码更改 umask () ->例如现在掩码是0002 我们设置权限777->与操作掩码0002 ->0775
5.关闭文件描述符(不是必须)
-
继承的打开文件不会用到,浪费系统资源,无法卸载
6.开始执行守护进程核心工作(必须)
守护进程退出处理程序模型
2.守护进程示例代码
//守护进程模型
//1.创建子进程 父进程退出
pid_t pid = -1;
int ret=-1;
pid = fork();
if (pid == -1)
{
perror("fork");
}
if (pid > 0)
{
exit(0);
}
///2.创建会话 脱离终端
pid=setsid();
if (-1 == pid)
{
perror("setsid");
}
//3.改变工作目录
ret = chdir("/");
if (-1 == ret)
{
perror("chdir");
return -1;
}
//4.设置权限掩码
umask(0);
//5.关闭文件描述符
close(0);
close(1);
close(2);
//6.核心代码
while (1)
{
system("date >> /tmp/txt.log");
sleep(2);
}
return 0;
3.获取当前系统时间的守护进程
#include <time.h>
time_t time(time_t *t);
返回从开始到现在的所有时间 以秒返回
时间转换函数
思路 ->localtime(&t) 返回的是一个结构体这个结构体内含有年月份
代码
pid_t pid = -1;
char file_name[SIZE];
time_t t = -1;
int ret=-1;
struct tm* pt = NULL;
while (1)
{
t = time(NULL);
if (-1 == t)
{
perror("time");
return 1;
}
pt = localtime(&t);
memset(file_name, 0, SIZE);
sprintf(file_name, "%s%d%d%d%d%d%d.log", "touch /home/as/asd/", pt->tm_year + 1900, pt->tm_mon + 1, pt->tm_mday, pt->tm_hour, pt->tm_min, pt->tm_sec);
system(file_name);
}
7.线程
1.线程的概念
线程是真正的程序执行实体 ,线程是轻量级的进程 底层都是clone
为了让进程完成一定的工作,进程必须至少包含一个线程。
进程可以认为是线程的容器
线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。
进程里的资源,线程都是有权访问的,比如说堆啊,栈啊,静态存储区什么的。
进程是操作系统分配资源的最小单位
线程是操作系统调度的最小单位
2.nptl
NPTL,或称为 Native POSIX Thread Library,是 Linux 线程的一个新实现,它克服了 LinuxThreads 的缺点,同时也符合 POSIX 的需求。与 LinuxThreads 相比,它在性能和稳定性方面都提供了重大的改进。
查看当前pthread库版本:getconf GNU_LIBPTHREAD_VERSION
3.线程的特点
线程是最小的执行单位 是轻量级进程 进程是最小的分配单位->底层都是clone开辟
进程可以蜕变未线程
线程可共享资源
1.文件描述符 2.每种信号的处理方式 3.当前工作目录 4.用户id和组id 5.内存地址空间
线程非共享资源
1.线程id 2.处理器线程和栈指针 3.栈空间 4.errno变量
4.线程操作函数
1.线程号
就像每个进程都有一个进程号一样,每个线程也有一个线程号。进程号在整个系统中是唯一的,但线程号不同,线程号只在它所属的进程环境中有效。
线程号则用 pthread_t 数据类型来表示,Linux 使用无符号长整数表示。
pthread_self函数:
#include <pthread.h>
pthread_t pthread_self(void);
功能:
获取线程号。
参数:
无
返回值:
调用线程的线程 ID 。
pthread_equal函数:
int pthread_equal(pthread_t t1, pthread_t t2);
功能:
判断线程号 t1 和 t2 是否相等。为了方便移植,尽量使用函数来比较线程 ID。
参数:
t1,t2:待判断的线程号。
返回值:
相等: 非 0
不相等:0
【注意】线程函数的程序在 pthread 库中,故链接时要加上参数 -lpthread。
2.线程的创建
pthread_create函数:
#include <pthread.h>
int pthread_create(pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void *),
void *arg );
功能:
创建一个线程。
参数:
thread:线程标识符地址。
attr:线程属性结构体地址,通常设置为 NULL。
start_routine:线程函数的入口地址。
arg:传给线程函数的参数。
返回值:
成功:0
失败:非 0
在一个线程中调用pthread_create()创建新的线程后,当前线程从pthread_create()返回继续往下执行,而新的线程所执行的代码由我们传给pthread_create的函数指针start_routine决定。
由于pthread_create的错误码不保存在errno中,因此不能直接用perror()打印错误信息,可以先用strerror()把错误码转换成错误信息再打印。
示例
void *thread_fun(void *arg)
{
sleep(1);
int num =(int)(long)arg;
printf("int the new thread: num = %d\n", num);
return NULL;
}
int main()
{
pthread_t tid;
int test=100;
int ret=-1;
ret=pthread_create(&tid,NULL,thread_fun,(void*)0x3);
while(1);
return 0;
}
编译 gcc xh.c -pthread
3.共享分析
线程资源共享
数据段和堆 全局变量 num共享
4.线程资源回收分析 pthread_join 阻塞回收
pthread_join函数:
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
功能:
等待线程结束(此函数会阻塞),并回收线程资源,类似进程的 wait() 函数。如果线程已经结束,那么该函数会立即返回。
参数:
thread:被等待的线程号。
retval:用来存储线程退出状态的指针的地址。
返回值:
成功:0
失败:非 0
示例
//写fifo1 读fifo2
void *thread_fun(void *arg)
{
sleep(1);
static int as=123;
int num =(int)(long)arg;
printf("int the new thread: num = %d\n", num);
return &as;
}
int main()
{
pthread_t tid;
int test=100;
int ret=-1;
void *value = NULL;
ret=pthread_create(&tid,NULL,thread_fun,(void*)0x3);
pthread_join(tid,&value);
printf("value = %d\n", *((int *)value));
while(1);
return 0;
}
5.线程分离 pthread_detach 不阻塞
一般情况下,线程终止后,其终止状态一直保留到其它线程调用pthread_join获取它的状态为止。但是线程也可以被置为detach状态,这样的线程一旦终止就立刻回收它占用的所有资源,而不保留终止状态。
不能对一个已经处于detach状态的线程调用pthread_join,这样的调用将返回EINVAL错误。也就是说,如果已经对一个线程调用了pthread_detach就不能再调用pthread_join了。
pthread_detach函数:
#include <pthread.h>
int pthread_detach(pthread_t thread);
功能:
使调用线程与当前进程分离,分离后不代表此线程不依赖与当前进程,线程分离的目的是将线程资源的回收工作交由系统自动来完成,也就是说当被分离的线程结束之后,系统会自动回收它的资源。所以,此函数不会阻塞。
参数:
thread:线程号。
返回值:
成功:0
失败:非0
6.线程退出 pthread_exit
在进程中我们可以调用exit函数或_exit函数来结束进程,在一个线程中我们可以通过以下三种在不终止整个进程的情况下停止它的控制流。
- 线程从执行函数中返回。
- 线程调用pthread_exit退出线程。
- 线程可以被同一进程中的其它线程取消。
pthread_exit函数:
#include <pthread.h>
void pthread_exit(void *retval);
功能:
退出调用线程。一个进程中的多个线程是共享该进程的数据段,因此,通常线程退出后所占用的资源并不会释放。
参数:
retval:存储线程退出状态的指针。
返回值:无
7.线程取消 pthread_cancel
注意:线程的取消并不是实时的,而又一定的延时。需要等待线程到达某个取消点(检查点)。
可粗略认为一个系统调用(进入内核)即为一个取消点。
ps: 退出线程后还是可以用join回收
5.线程属性 ->分离状态设置
Linux下线程的属性是可以根据实际项目需要,进行设置,之前我们讨论的线程都是采用线程的默认属性,默认属性已经可以解决绝大多数开发时遇到的问题。
如我们对程序的性能提出更高的要求那么需要设置线程属性,比如可以通过设置线程栈的大小来降低内存的使用,增加最大线程个数。
typedef struct
{
int etachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void* stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
} pthread_attr_t;
主要结构体成员:
- 线程分离状态
- 线程栈大小(默认平均分配)
- 线程栈警戒缓冲区大小(位于栈末尾)
- 线程栈最低地址
线程的分离状态决定一个线程以什么样的方式来终止自己。
- 非分离状态:线程的默认属性是非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
- 分离状态:分离线程没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。应该根据自己的需要,选择适当的分离状态。
相关函数:
#include <pthread.h>
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:设置线程分离状态
参数:
attr:已初始化的线程属性
detachstate: 分离状态
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD_CREATE_JOINABLE(非分离线程)
返回值:
成功:0
失败:非0
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
功能:获取线程分离状态
参数:
attr:已初始化的线程属性
detachstate: 分离状态
PTHREAD_CREATE_DETACHED(分离线程)
PTHREAD _CREATE_JOINABLE(非分离线程)
返回值:
成功:0
失败:非0
这里要注意的一点是,如果设置一个线程为分离线程,而这个线程运行又非常快,它很可能在pthread_create函数返回之前就终止了,它终止以后就可能将线程号和系统资源移交给其他的线程使用,这样调用pthread_create的线程就得到了错误的线程号。
要避免这种情况可以采取一定的同步措施,最简单的方法之一是可以在被创建的线程里调用pthread_cond_timedwait函数,让这个线程等待一会儿,留出足够的时间让函数pthread_create返回。
设置一段等待时间,是在多线程编程里常用的方法。但是注意不要使用诸如wait()之类的函数,它们是使整个进程睡眠,并不能解决线程同步的问题。
pthread_t tid;
pthread_attr_t attr;
int test=100;
int ret=-1;
void *value = NULL;
pthread_attr_init(&attr);//状态初始化
pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);//设置分离状态
ret=pthread_create(&tid,&attr,thread_fun,NULL);
线程注意事项
- 主线程退出其他线程不退出,主线程应调用pthread_exit
- 避免僵尸线程
a) pthread_join
b) pthread_detach
c) pthread_create指定分离属性
被join线程可能在join函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
- malloc和mmap申请的内存可以被其他线程释放
- 应避免在多线程模型中调用fork,除非马上exec,子进程中只有调用fork的线程存在,其他线程t在子进程中均pthread_exit
- 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制