5守护进程与线程

进程组

多个进程的集合,第一个进程就是组长,组长进程的PID等于进程组ID。

进程组生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。与组长进程是否终止无关。

一个进程可以为自己或子进程设置进程组 ID

相关函数

pid_t getpgrp(void);
得到当前进程所在的进程组的组 ID

pid_t getpgid(pid_t pid);
获取指定的进程所在的进程组的组 ID,参数 pid 就是指定的进程,0当前进程

int setpgid(pid_t pid, pid_t pgid);
将某个进程移动到其他进程组中或者创建新的进程组

参数:
	pid: 某个进程的进程 ID
	pgid: 某个进程组的组 ID
	如果 pgid 对应的进程组存在,pid 对应的进程会移动到这个组中,pid != pgid
	如果 pgid 对应的进程组不存在,会创建一个新的进程组,因此要求 pid == pgid, 当前进程就是组长了


返回值:
	函数调用成功返回 0,失败返回 - 1

setpgid设置子进程组id

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
        pid_t pid;

        pid = fork();
        if(pid == 0 )
        {
                printf("我是子进程:%d,我的进程组id是:%d\n",getpid(),getpgrp());
                setpgid(pid,pid);  //设置子进程的组id为自己的pid,默认是父进程的pid
                printf("我是子进程:%d,我的进程组id是:%d\n",getpid(),getpgrp());
        }
        else if(pid > 0)
        {
               
                sleep(1);
                printf("我是父进程:%d,我的进程组id是:%d\n",getpid(),getpgrp());
                wait(NULL);
        }
        return 0;
}

会话

多个进程组的集合

获取进程所属的会话 ID
pid_t getsid(pid_t pid); 
成功:返回调用进程的会话 ID;失败:-1,设置 errno
pid 为 0 表示察看当前进程 session ID


创建一个会话,并以自己的 ID 设置进程组 ID,同时也是新会话的 ID。
pid_t setsid(void); 
成功:返回调用进程的会话 ID;失败:-1,设置 errno
调用了 setsid 函数的进程,既是新的会长,也是新的组长。

注意事项:

调用进程不能是进程组组长,该进程变成新会话首进程(session header),建立新会话时,先调用 fork, 父进程终止,子进程调用 setsid(),
#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{

        pid_t pid;


        pid = fork();
        if(pid == 0)
        {
                printf("我是子进程id:%d,进程组id:%d,会话id:%d\n",getpid(),getpgid(0),getsid(0));
                printf("change--------------\n");
                sleep(2);
                setsid();
                printf("我是子进程id:%d,进程组id:%d,会话id:%d\n",getpid(),getpgid(0),getsid(0));
        }
        else{

                sleep(3);
                printf("我是父进程id:%d,进程组id:%d,会话id:%d\n",getpid(),getpgid(0),getsid(0));

        }
        return 0;
}

编译执行结果

我是子进程id:44801,进程组id:44800,会话id:39560  #默认子进程的进程组id是父进程的pid,会话id是当前bash的pid
change--------------
我是子进程id:44801,进程组id:44801,会话id:44801  #setsid后,进程组id,会话id都变成子进程的pid
我是父进程id:44800,进程组id:44800,会话id:39560

守护进程

daemon进程。通常运行与操作系统后台,脱离控制终端。一般不与用户直接交互。周期性的等待某个事件发生或周期性执行某一动作。

不受用户登录注销影响。通常采用以d结尾的命名方式。

Linux 后台的一些系统服务进程,没有控制终端,不能直接和用户交互。不受用户登录、注销的影响,一直在运行着,他们都是守护进程。

创建守护进程

创建守护进程,最关键的一步是调用 setsid 函数创建一个新的 Session,并成为 Session Leader。

1. fork子进程,让父进程终止。

2. 子进程调用 setsid() 创建新会话

3. 通常根据需要,改变工作目录位置 chdir(), 防止目录被卸载。

4. 通常根据需要,重设umask文件权限掩码,影响新文件的创建权限。  022 -- 755	0345 --- 432   r---wx-w-   422

5. 通常根据需要,关闭/重定向 文件描述符

6. 守护进程 业务逻辑。while()
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/time.h>
#include <string.h>

void func(int signo)
{
        int fd1;

        fd1 = open("./lc.txt",O_RDWR|O_CREAT|O_APPEND,0664);  //fd1 = 3 符合默认的习惯
        if(fd1 == -1 ){perror("open lc.txt error");exit(1);}

        char *str="heppy new year\n";
        write(fd1,str,strlen(str));
        close(fd1);
}


int main(int argc, char *argv[])
{
        pid_t pid;
        int fd,fd1;

        pid = fork();
        if(pid > 0){exit(1);}

        pid_t pid1 = setsid();
        if(pid1 == -1 ){perror("open lc.txt error");exit(1);}


        chdir("/home/lc");  //切换到一个不可能被删除卸载的目录

        umask(0022); //默认umask是0002,默认创建普通文件默认属性是664
        close(STDIN_FILENO);  //close 0

        fd = open("/dev/null",O_RDWR);    //fd = 0
        if(fd == -1 ){perror("open /dev/null error");exit(1);}

        dup2(fd, STDOUT_FILENO); // 重定向 stdout和stderr  1 -->0
        dup2(fd, STDERR_FILENO); // 2--> 0,

//捕捉信号
        struct sigaction act;
        act.sa_handler = func;
        act.sa_flags = 0;
        sigemptyset(&act.sa_mask);
        sigaction(SIGALRM,&act,NULL);
//      signal(SIGALRM,func);

//设置定时器
        struct itimerval it;
        it.it_interval.tv_sec = 10;
        it.it_interval.tv_usec = 0;
        it.it_value.tv_sec = 10;
        it.it_value.tv_usec = 0;
        setitimer(ITIMER_REAL,&it,NULL);
        
        while (1);              // 模拟 守护进程业务.
        return 0;
}

线程

LWP,(light weight process)轻量级的进程,在linux环境下线程的本质仍然是进程,

进程:有独立的 进程地址空间。有独立的pcb。	分配资源的最小单位。可看成是只有一个线程的进程

线程:有独立的pcb。没有独立的进程地址空间。	最小单位的执行。
[root@lc133 ~]# ps -aux | grep mysql | grep -v grep
mysql      1620  0.4 20.2 1793004 405880 ?      Ssl  08:20   1:36 /usr/sbin/mysqld
Ssl:休眠,拥有子进程,多线程
[root@lc133 ~]# ps -Lf 1620
UID         PID   PPID    LWP  C NLWP STIME TTY      STAT   TIME CMD
mysql      1620      1   1620  0   38 08:20 ?        Ssl    0:01 /usr/sbin/mysqld
mysql      1620      1   1669  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1670  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1671  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1672  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1674  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1675  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1676  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1677  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1678  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1679  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1680  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1724  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1725  0   38 08:20 ?        Ssl    0:04 /usr/sbin/mysqld
mysql      1620      1   1726  0   38 08:20 ?        Ssl    0:04 /usr/sbin/mysqld
mysql      1620      1   1727  0   38 08:20 ?        Ssl    0:04 /usr/sbin/mysqld
mysql      1620      1   1728  0   38 08:20 ?        Ssl    0:04 /usr/sbin/mysqld
mysql      1620      1   1729  0   38 08:20 ?        Ssl    0:55 /usr/sbin/mysqld
mysql      1620      1   1786  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1787  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1788  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1793  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1794  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1797  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1799  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1802  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1803  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1804  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1811  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1812  0   38 08:20 ?        Ssl    0:05 /usr/sbin/mysqld
mysql      1620      1   1814  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1815  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1816  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1817  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1830  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1831  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1832  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld
mysql      1620      1   1836  0   38 08:20 ?        Ssl    0:00 /usr/sbin/mysqld

lwp:线程号

系统会给mysql进程1620分配进程地址空间,内核空间拥有pid1620的pcb。

pid1620进程创建线程时,本身也成了主线程。创建的线程分别拥有自己独立的pcb,处在同一内核空间中。


	线程更加节省系统资源,效率不仅可以保持的,而且能够更高
	
	在一个地址空间中多个线程独享:每个线程都有属于自己的栈区,寄存器 (内核中管理的)
	1.线程 id
	2.处理器现场和栈指针(内核栈)
	3.独立的栈空间(用户空间栈)
	4.errno 变量
	5.信号屏蔽字
	6.调度优先级
	
	在一个地址空间中多个线程共享:代码段,堆区,全局数据区,打开的文件 (文件描述符表) 都是线程共享的
	1.文件描述符表
	2.每种信号的处理方式
	3.当前工作目录
	4.用户 ID 和组 ID
	5.内存地址空间 (.text/.data/.bss/heap/共享库)
	
	每个进程对应一个虚拟地址空间,一个进程只能抢一个 CPU 时间片
	
	一个地址空间中可以划分出多个线程,在有效的资源基础上,能够抢更多的 CPU 时间片

	一个进程创造线程并不是越多越好

优点: 1. 提高程序并发性 2. 开销小 3. 数据通信、共享数据方便
缺点: 1. 库函数,不稳定 2. 调试、编写困难、gdb 不支持 3. 对信号支持不好
优点相对突出,缺点均不是硬伤。Linux 下由于实现方法导致进程、线程差别不是很大。

pthread_self获取当前线程id

获取线程 ID。其作用对应进程中 getpid() 函数。

pthread_t pthread_self(void);返回值:成功:0; 失败:无!

线程 ID:pthread_t 类型,本质:在 Linux 下为%lu无符号整数,其他系统中可能是结构体实现

线程 ID 是进程内部,识别标志。(两个进程间,线程 ID 允许相同)

注意:在子线程中通过使用pthread_self,获得线程id。

pthread_create创建线程

创建一个新线程。 其作用对应进程中 fork() 函数。

int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);

返回值:成功:0; 失败:错误号 -----Linux 环境下,所有线程特点,失败均直接返回错误号。

参数:
	pthread_t:当前 Linux 中可理解为:typedef unsigned long int pthread_t;
	参数 1:传出参数,保存系统为我们分配好的线程 ID
	参数 2:通常传 NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
	参数 3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
	参数 4:线程主函数执行期间所使用的参数。传参。

pthread_exit退出当前线程

	void pthread_exit(void *retval);  退出当前线程。

	参数:线程退出的时候携带的数据,当前子线程的主线程会得到该数据。如果不需要使用,指定为 NULL

几个退出函数:
		exit();	退出当前进程。后续代码不会执行

		return: 返回到调用者那里去。

		pthread_exit(): 退出当前线程。不影响其他进程

循环创建线程

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void *func(void *arg)
{
        int i = (int)arg;
        printf("我是第%d子线程线程id:%lu,主线程是:%d\n",i+1,pthread_self(),getpid());
        //return NULL;
        if(i==4){while(1);}
        pthread_exit(NULL);
}


int main(int argc, char *argv[])
{

        pthread_t tid;

        int i;
        for(i = 0; i < 5; i++){
                int ret = pthread_create(&tid,NULL,func,(void*)i);
                if(ret != 0){perror("pthread_create error");exit(1);}
        }
        //sleep(1);
        printf("我是主线程线程id:%lu,主线程是:%d\n",pthread_self(),getpid());
        //while(1);
        //return 0;
        pthread_exit(NULL);
}
主线程 pthread_exit(NULL);只退出当先线程,不会对其他线程造成影响。
[lc@lc133 pthread]$ gcc pthread.c -lpthread
pthread.c: 在函数‘func’中:
pthread.c:8:10: 警告:将一个指针转换为大小不同的整数 [-Wpointer-to-int-cast]
  int i = (int)arg;
          ^
pthread.c: 在函数‘main’中:
pthread.c:23:43: 警告:将一个整数转换为大小不同的指针 [-Wint-to-pointer-cast]
   int ret = pthread_create(&tid,NULL,func,(void*)i);
                                           ^
[lc@lc133 pthread]$ ./a.out
我是主线程线程id:140307387344704,主线程是:25356
我是第3子线程线程id:140307362223872,主线程是:25356
我是第2子线程线程id:140307370616576,主线程是:25356
我是第1子线程线程id:140307379009280,主线程是:25356
我是第4子线程线程id:140307353831168,主线程是:25356
我是第5子线程线程id:140307345438464,主线程是:25356

[root@lc133 ~]# ps -aux | grep a.out  #使用pthread_exit主线程已退出,子线程还在,使用return函数,主线程退出,子进程也会退出
lc        25356  100  0.0      0     0 pts/1    Zl+  14:51   0:58 [a.out] <defunct>
root      25373  0.0  0.1 112840  2512 pts/0    S+   14:52   0:00 grep --color=auto a.out

pthread_join阻塞 回收线程

int pthread_join(pthread_t thread, void **retval);	阻塞 回收线程。

		thread: 待回收的线程id

		retval:传出参数。 回收的那个线程的退出值。

			线程异常借助,值为 -1。

		返回值:成功:0

			失败:errno

pthread_exit可以传出指针,数值,要注意数据格式的强转。

#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

struct student
{
        int age;
        char name[256];
};

void *func(void *arg)
{
        printf("child thread,process id is %d,thread id is %lu\n",getpid(),pthread_self());

        struct student *luci;
        luci = malloc(sizeof(struct student));
        memset(luci,0,sizeof(luci));

        luci->age = 17;
        strcpy(luci->name,"luci");
        pthread_exit((void *)luci);
}

int main(int argc, char *argv)
{

        printf("main thread,process id is %d,thread id is %lu\n",getpid(),pthread_self());
        pthread_t tid;

        int ret = pthread_create(&tid,NULL,func,NULL);
        if(ret != 0)
        {perror("pthread_creade error");exit(1);}

        struct student *retval;
        //pthread_join 传出参数retval是void**类型的
        ret = pthread_join(tid,(void **)&retval);
        if(ret != 0)
        {perror("pthread_jion error");exit(1);}

        printf("child thread exit.\n");
        printf("age = %d ,name = %s \n",retval->age,retval->name);
        return 0;
}
#include <stdio.h>
#include <pthread.h>

void *func(void *arg)
{
        printf("child pthread id : %lu\n",pthread_self());

        pthread_exit((void*)66);
}

int main(){

        pthread_t tid;
        int *retval;

        pthread_create(&tid,NULL,func,NULL);

        pthread_join(tid,(void**)&retval) ;

        printf("main pthread receive : %d\n",(void *)retval);
        pthread_exit(NULL);
}

pthread_detach 设置线程分离

	int pthread_detach(pthread_t thread);		设置线程分离

		thread: 待分离的线程id

		返回值:成功:0

			失败:errno	
#include <stdio.h>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>

void *func(void *arg)
{
        int i;
        for( i = 0;i < 5; i++)
        {
                sleep(1);
                printf("i = %d \n",i);
        }
        pthread_exit(NULL);

}
int main()
{
        pthread_t tid;
        int ret;

        ret = pthread_create(&tid,NULL,func,NULL);
        if(ret != 0)
        {
        fprintf(stderr,"pthread_create error:%d",strerror(ret));
        exit(1);
        }

        pthread_detach(tid);
        ret = pthread_join(tid,NULL);//线程分离后主线程就不能回收了,报错:无效的参数
        if(ret != 0)
        {
        fprintf(stderr,"pthread_join error:%s",strerror(ret));
        exit(1);
        }
        pthread_exit(NULL);
        return 0;
}

pthread_cancel杀死一个线程

	int pthread_cancel(pthread_t thread);		杀死一个线程。  需要到达取消点(保存点)

		thread: 待杀死的线程id
		
		返回值:成功:0

			失败:errno
			
		在线程 A 中调用线程取消函数 pthread_cancel,指定杀死线程 B,这时候线程 B 是死不了的,只有在线程 B 中进程进行一次系统调用(从用户区切换到内核区),这个节点会被pthread_cancel杀死,否则线程 B 可以一直运行。与信号不同,进程会优先处理信号。

		如果,子线程没有到达取消点, 那么 pthread_cancel 无效。

		我们可以在程序中,手动添加一个取消点。使用 pthread_testcancel();

		成功被 pthread_cancel() 杀死的线程,返回 -1.使用pthead_join 回收。

pthread_equal判断线程id是否相同

	int pthread_equal(pthread_t t1, pthread_t t2);
	
	参数:t1 和 t2 是要比较的线程的线程 ID
	
	返回值:如果两个线程 ID 相等返回非 0 值,如果不相等返回 0

与进程函数相比

	线程控制原语					进程控制原语


	pthread_create()				fork();

	pthread_self()					getpid();

	pthread_exit()					exit(); 		/ return 

	pthread_join()					wait()/waitpid()

	pthread_cancel()				kill()

	pthread_detach()

创建线程时设置线程分离属性

pthread_create函数中参数 pthread_attr_t 是一个结构体

typedef struct
{
       int                       detachstate;   // 线程的分离状态
       int                       schedpolicy;   // 线程调度策略
       structsched_param         schedparam;    // 线程的调度参数
       int                       inheritsched;  // 线程的继承性
       int                       scope;         // 线程的作用域
       size_t                    guardsize;     // 线程栈末尾的警戒缓冲区大小
       int                       stackaddr_set; // 线程的栈设置
       void*                     stackaddr;     // 线程栈的位置
       size_t                    stacksize;     // 线程栈的大小
} pthread_attr_t;
	步骤:
	
	pthread_attr_t attr  	创建一个线程属性结构体变量

	pthread_attr_init(&attr);	初始化线程属性,成功:0;失败:错误号

	pthread_attr_setdetachstate(&attr,  PTHREAD_CREATE_DETACHED);设置线程属性为 分离态
	
	//detachstate: PTHREAD_CREATE_DETACHED(分离线程)
	//			  PTHREAD _CREATE_JOINABLE(非分离线程)
	//获取程属性,分离 or 非分离
	//int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate); 
	//参数: attr:已初始化的线程属性

	
	pthread_create(&tid, &attr, tfn, NULL); 借助修改后的 设置线程属性 创建为分离态的新线程

	pthread_attr_destroy(&attr);	销毁线程属性,成功:0;失败:错误号
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>

void *func(void *arg)
{
        printf("hello wrold.\n");
        pthread_exit(NULL);
}


int main()
{
        pthread_attr_t attr;

        pthread_attr_init(&attr);

        pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);

        int val;

        pthread_attr_getdetachstate(&attr,&val);

        printf("属性:%d\n",val);

        pthread_t tid;

        pthread_create(&tid,&attr,func,NULL);

        pthread_attr_destroy(&attr);

        sleep(1);
        int ret = pthread_join(tid,NULL);
        if(ret != 0)
        {
                fprintf(stderr,"pthread_join error : %s\n",strerror(ret));
                exit(1);
        }
        pthread_exit(NULL);

        return 0;
}

[lc@lc pthread]$ ./a.out
属性:1
hello wrold.
pthread_join error : Invalid argument
//pthread_join报错成功分离

注意事项

1. 主线程退出其他线程不退出,主线程应调用 pthread_exit
2. 避免僵尸线程
pthread_join
pthread_detach
pthread_create 指定分离属性
被 join 线程可能在 join 函数返回前就释放完自己的所有内存资源,所以不应当返回被回收线程栈中的值;
3. malloc 和 mmap 申请的内存可以被其他线程释放
4. 应避免在多线程模型中调用 fork 除非,马上 exec,子进程中只有调用 fork 的线程存在,其他线程在子进程
中均 pthread_exit
5. 信号的复杂语义很难和多线程共存,应避免在多线程引入信号机制
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值