详解——进程与线程

进程:正在进行/运行的程序                       动态

程序:它是存放在硬盘上的可执行文件       静态

任务:进程工作的目的就是完成任务,一般我们把进程和任务等价

当你在终端输入./a.out的时候,终端首先开辟了一片内存,将a.out文件内容拷贝到内存中,然后执行内存的程序,形成进程

如何查看进程/任务:ps -aux,  process stat   -a: 所有进程

杀死/结束进程:kill -9 +pid(进程号)

pid:process id 每个进程都要有一个进程号

%CPU和%MEM:CPU和内存消耗

COMMAND:进程的名字

一、进程

执行新的任务,一个程序,需要创建多个进程,竞争更多的CPU资源

1.fork()创建子进程

函数原型:int fork(void);

返回值:

        -1 表示失败, 克隆失败 ,当前只有一个进程

        0,表示 当前进程是一个 子进程

         >0: 表示 父进程, 返回值是 孩子的 pid号

 实现代码:

#include <stdio.h>

#include <unistd.h>

int main()

{

    int aa = 1;//当前的主进程

    printf("mypid = %d\n",getpid() );



    int subpid = fork(); //之后会生成一个一模一样的进程

    if(subpid ==0){

    while(1){

        printf("I am a child,pid=%d\n",getpid());

        aa++; 
        sleep(1);

        }

        }
        else if(subpid >0){

        while(1){

        printf("I am father, pid=%d subpid=%d aa=%d\n",getpid(), subpid ,aa);


        sleep(1);

                }

                        }

    }

2.进程的终结

(1)return 退出main, 本质上其实调用exit( )

#include <stdio.h>

#include <stdlib.h>

int main()

{

    for(int i=0;i<100;i++){

    if(i==5){

        exit(6);  

            }

    printf("main i=%d\n",i);

}

    return 0;

}

(2)进程调用exit主动结束自己

       函数原型:

        #include <stdlib.h>

        void exit(int status);

exit会释放掉进程绝大部分资源,但是留有尸体在系统中,尸体中保存了退出码status等待父亲的收尸。父亲收尸彻底清理掉所有资源,获取退出码 

status  :

         0 ,正常退出

        其他的码,表示不同的原因

(3)_exit(int status)

和exit区别:    eixt函数其实内部调用_exit实现进程推出的,但是exit在调用_exit结束进程之前,会做一些清理工作,主要是清理文件缓存到硬盘。比_exit安全。

总结:

其实上面三种在方法,最终都是调用_exit函数,保留尸体在系统中。exit函数比较安全版本,建议使用

(4)wait函数

父进程调用 wait函数来收尸。如果父进程先死,子进程会托付给 爷爷进程,最终可以托付给init进程。

函数原型:

 #include <sys/types.h>
 #include <sys/wait.h>

  pid_t wait(int *status);
        阻塞的等待任一子进程退出,一旦发现子进程退出该函数返回,返回值就是那个退出的子进程的pid  ,同时给子进程收尸,彻底释放子进程所有资源。 如果子进程先退出,立马获取返回, status保存了子进程的退出码,保存在了 8-15位

实现代码:


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

#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main()
{
	printf("I am main pid=%d\n",getpid() );
	
	int subpid = fork();
	if(subpid <0){
		printf("I am main,fork failed\n");
		return -34;
	}
	
	/*以下代码 两个进程在执行*/
	if(subpid >0){
		/* main process code */
		int status;
		int spid = wait(&status);
		if(spid <0){
			perror("wwait err");
			exit(0);		//结束主进程 自己.
		}
		printf("subpid=%d terminated err code =%d\n",spid, (status >>8)&0xFF );
		
		sleep(1);
		exit(0);
	}else {
		/* sub process code */		
		printf("I am child pid=%d ppid=%d\n",getpid(),getppid());
		sleep(1);
		exit(8);		//结束自己,残留尸体在系统中. 传递值 给父进程
	}
	
}

3.exec族

读取文件中的内容覆盖子进程本身,然后执行子进程

exec函数族

        fork()函数用于创建一个子进程,该子进程几乎复制了父进程的全部内容。我们能否让子进程执行一个新的程序呢?exec函数族就提供了一个在进程中执行另一个程序的方法。它可以根据指定的文件名或目录名找到可执行文件,并用他来取代当前进程的数据段、代码段和堆栈段。在执行完后,当前进程除了进程号外,其他内容都被替换。

l(list) : 表示参数采用列表;

v(vector) : 参数用数组;

p(path) : 有p自动搜索环境变量PATH;

e(env) : 表示自己维护环境变量;

(1)#include <unistd.h>

        int execl(const char *path, const char *arg0,char *arg1 ,... , (char  *) NULL );

 l:list  参数以list列表的形式展示

 path:程序本身 sub.out

 arg0,arg1 ......表示给程序的传参,分别给子程序 argv[0] argv[1] ...

(2)int execv(const char *path, char *const argv[]);

v:vector矢量 数组, 参数以数组的形式展示

返回值:

 success: 永远不会返回了,因为子进程全都被替换了,包括exec函数

 -1: errno

 execl 和execv的区别只不过是execv把execl以列表的形式传参变成了以数组形式的传参

实现代码:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>

int main()
{
	printf("I am main process pid=%d\n",getpid() );
	
	int subpid = fork();
	if(subpid == 0){
		char *argv[] = {"-s","-l","-t",NULL };	//最后一个一定要为NULL,表示结束
		//int ret = execl( "./sub.out","-s","-l","-t",NULL );//最后一个参数必须为NULL
		int ret = execv("./sub.out", argv );
		if(ret<0){
			perror("execl eerr");
			exit(9);
		}
		
	}else if(subpid > 0 ){
		while(1){
			printf("I am main process\n");
			sleep(1);
		}
	}
}



Sub.c:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
int main(int argc, char **argv)
{
	
	for(int i=0; i<argc;i++){
		printf("subp  %dst arg=%s\n",i,argv[i]);
	}
	
	while(1){
		printf("I am subp,pid=%d ppid=%d\n",getpid(),getppid() );
		sleep(2);
	}
	
	return 0;
}

运行结果如下图所示:

4.进程间的通信方式

1.管道  

它其实就是一个共享的特殊文件,称为管道文件

无名管道 : 也被淘汰,它只能用于父子进程间进行通信

有名管道 : 它是一个特殊的临时文件,用在任意进程间通信  单向的  FIFO  先进先出

(1)创建 pipe文件

函数原型:

 #include <sys/types.h>

 #include <sys/stat.h>

 int mkfifo(const char *pathname, mode_t mode);

pathname:指定文件路径 /tmp/communicatexxxx

mode :  0666,文件权限

返回值:

          0-success

         -1:errno

errno:如果是因为 文件已存在 而报错,那么忽略.

EEXIST:表示 文件已存在

(2)打开文件open

(3)不断地 写入信息

(4)close

(5)unlink(  )

实现代码:

comm.h:包含一些头文件供发送端和接收端使用

Comm.h:
#ifndef  COMM_HHH

#define  COMM_HHH

struct msg_struct {

int temp;

long humiduty;

char windspeed;

char wind_direction[16];

char des[128];

};

#endif

发送方:我们在发送方创建一个命名管道文件,然后以只写的方式打开,之后接收端就可以从该命名管道读取信息

#include <unistd.h>

#include <stdio.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include "comm.h"


int main(void)

{

    int ret;

    ret = mkfifo("/tmp/communicatexxxx",0666); //创建文件

    if(ret < 0 ){

        printf("mkfifo errno=%d des:%s\n",errno,strerror(errno));

            if(errno != EEXIST ){

            return -1;

                                }

                }


int fd = open("/tmp/communicatexxxx",O_WRONLY);

    if(fd < 0 ){

        perror("open err");

        return -3;

                }

    struct msg_struct msg;

    msg.temp=23;
    
    msg.humiduty = 78;

    msg.windspeed = 5;

    strcpy(msg.wind_direction,"eastsouth");

    strcpy(msg.des,"cloudy");


while(1){

    msg.temp++;

    msg.humiduty-=3;

    msg.windspeed++;

    ret = write(fd,&msg,sizeof(msg));

    if(ret<0){

        perror("write err");

        return -45;

            }

    sleep(1);

        }

    close(fd);

    unlink("/tmp/communicatexxxx");

    return 0;

}

接收方:创建一个文件以只读的方式打开命名管道文件,实现和发送方的通信


#include <unistd.h>

#include <stdio.h>

#include <errno.h>

#include <string.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include "comm.h"


int main(void)

{

    int ret;
    
    ret = mkfifo("/tmp/communicatexxxx",0666); //创建文件

    if(ret < 0 ){

        printf("mkfifo errno=%d des:%s\n",errno,strerror(errno));

        if(errno != EEXIST ){

            return -5;

                            }

                }

int fd = open("/tmp/communicatexxxx",O_RDONLY);

    if(fd < 0 ){

        perror("open err");

        return -3;

            }
    struct msg_struct msg;

    while(1){

        memset(&msg,0,sizeof(msg));

        /*如果文件没有数据,则 死等*/

        ret = read(fd,&msg,sizeof(msg));

    if(ret<0){

        perror("read err");
    
        return -45;

            }

    printf("recv got data:temp%d  humidtiy%ld windspeed%d wind_drect=%s\n",\

    msg.temp,msg.humiduty,msg.windspeed,msg.wind_direction);

    sleep(1);

    }

    close(fd);

    unlink("/tmp/communicatexxxx");

    return 0;

}

2.共享内存

(1)创建共享内存

创建/获取 一片共享内存,当指定的magicid的内存不存在的时候,则创建,如果存在 获取。

int shmget( magicID ,  size, )

magicID:创建内存,并指定该内存的 id.

int shmget(key_t key, size_t size, int shmflg);

keyid: 当指定的magicid的内存不存在的时候,则创建 如果存在 获取

size: 大小

shmflg: 指定共享内存的权限    一般是0666,他还可以 指定本次是创建还是获取。

0666|IPC_CREAT 如果没有,说明本次shmget只会获取, 否则没有则创建

返回值: 是共享内存的描述符,是一个正数,我们以后就使用该id来访问共享内存.

             失败:  -1 ,errno

(2)将共享内存,绑定到当前进程,进程就可以通过一个地址来访问了

           #include <sys/types.h>

          #include <sys/shm.h>

void *shmat(int shmid,     const void *shmaddr, int shmflg);

shmid:共享内存描述符,它代表了这个共享内存

shmaddr: 进程也可以指定一个地址映射到这片共享内存,但是一般为NULL,将会有系统 自动分配一个地址.

shmflg::0666权限

返回值:   返回一个地址,进程使用该地址 访问共享内存的指针

              失败返回   (void*)-1       errno

(3)访问

(4)解绑定: 断开进程和共享内存的连接

 int shmdt(const void *shmaddr);

        shmaddr:代表了那片地址空间

返回值:

        0 success

        -1:errno

(5)销毁共享内存

        它是一个 多功能函数,可以 获取/设置/修改共享内存的信息,同样还可以删除该共享内存

        int shmctl(int shmid, int cmd, struct shmid_ds *buf);

cmd:  

IPC_RMID :表示删除该内存段,如果你要删除 不需要参数buf==NULL

IPC_STAT:获取共享内存信息

IPC_SET::设置修改共享内存信息

返回值:

        0-success

        -1:errno

实现代码

Comm.h:

#ifndef  COMM_H

#define COMM_H

struct msg_struct {

int temp;

int humidity;

char wind;

char des[16];

};

#define SHM_MAGICID 1646466146

#endif

发送方:

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include "comm.h"



int main()

{

    int ret;

    int shmid = shmget( SHM_MAGICID ,256, 0666|IPC_CREAT  ); //IPC_CREATE 没有就创建

    if(shmid <0){

        perror("shget create failed");

        return -45;

                }



/*将共享内存绑定到 进程,进程获取 访问共享内存的指针  */

    struct msg_struct  *pshm = shmat(shmid,NULL,0666);

        if(  pshm  == ( (void*)-1  )  ){

        perror("shmat err");

        return -34;

                                    }



    pshm->data_flags = 0;

    pshm->temp = 35;

    pshm->humidity = 58;

    pshm->wind = 5;

    strcpy(pshm->des,"sunnday");

    while(1){

        if(pshm->data_flags == 0){ //如果发现没有数据,则写入

            pshm->temp++;

            pshm->humidity+=2;

            pshm->wind+=4;

            //写完毕

            pshm->data_flags = 1;

        }



    usleep(1000 * 300); /* ns us  ms  s  */

    }


    ret = shmdt(pshm);

   if(ret<0){

        perror("shmdt err");
        
        return -45;
    
            }


    /* 销毁*/

    ret = shmctl(shmid, IPC_RMID,NULL);

    if(ret<0){
    
        perror("shmdel err");

        return -45;

            }
    return 0;

}

接收方:

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include "comm.h"



int main()

{

    int ret;

    int shmid = shmget( SHM_MAGICID ,256, 0666|IPC_CREAT  ); //IPC_CREATE 没有就创建

    if(shmid <0){

        perror("shget create failed");

        return -45;

            }


/*将共享内存绑定到 进程,进程获取 访问共享内存的指针  */

    struct msg_struct  *pshm = shmat(shmid,NULL,0666);

    if(  pshm  == ( (void*)-1  )  ){

            perror("shmat err");

            return -34;

                            }

while(1){

    if(pshm->data_flags == 1){ //如果有数据则读出来

        printf("reader got:temnp%d huim%d wind=%d des:%s\n", \

        pshm->temp,pshm->humidity,pshm->wind,pshm->des);

        pshm->data_flags = 0;

                        }

    usleep(1000* 1000);

       }



    ret = shmdt(pshm);

        if(ret<0){

            perror("shmdt err");

            return -45;

                }
    


    /* 销毁*/

    ret = shmctl(shmid, IPC_RMID,NULL);

    if(ret<0){

        perror("shmdel err");

        return -45;

        }

    return 0;

}

3.信号

信号有很多种,每个信号都有一个 int id

SIGHUP:当终端关闭的时候,回想所有的 子进程发送该信号,进程收到该信号,默认 自杀

SIGINT:当你ctrl+c的时候,系统会给该进程发送一个SIGINT,进程收到该信号后自杀

SIGEVG:段错误  当进程访问非法地址,系统会给该进程发送这个信号,进程收到后自杀

SIGKILL:kill -9 pid,,kill是个命令,作用是发送信号  kill  -信号编号  进程pid,进程收到信 号后自杀该信号不允许改变处理方式

SIGCHID:当子进程死掉的时候,会主动给父进程发送信号,父进程收到信号后会调用wait 函数来收尸

SIGALRM:定时器信号

SIGTSTP:当你ctrl+z的时候,系统给进程发送该信号

SIGUSR1/SIGUSR2:用户可以自定义的功能

进程收到信号之后,每个信号都有默认的动作: 一般都是自杀

进程也可以自己制定当收到某种信号的时候,自己的处理动作

进程要 指定 收到某种信号之后的动作  

   #include <signal.h>

   typedef void (*sighandler_t)(int);

  sighandler_t signal(int signum, sighandler_t handler);

signum:表示具体的某个信号

handler:代表 要处理该信号的函数 ,原型   void  signal_handlex (int signum)

返回值:  

        成功     返回handle指针

        失败    返回 SIG_ERR    (void*)-1

信号的应用:

(1)进程捕获进程执行自己的操作

(2) 定时器信号 SIGALRM  14

原理: 系统每隔一段时间给进程发送 SIGALRM信号,这样进程每隔一段时间,就会执行一次对应的代码

(3)子进程exit会给父进程发送 SIGCHLD ,父进程收到该信号后,再去wait收尸, 就不会卡住

父进程:

        void signal_handle(int signum)

                {

                        pid = wait(&exitcode);

                        其他处理

                }

int main()

        {

        signal(SIGCHLD, signal_handle)

        .............

        pid = fork();

        if(pid==0){ //子进程

                exec("sub.out");

                }else { 父进程

                .....

        //wait(); 以前父进程死等收尸

        ........

                        }

        }

(4)后台服务器进程

当终端关闭,你的子程序会收到 SIGHUP信号,然后自杀

方法一:你的程序 捕获SIGHUP信号,就可以了

方法二:nohup  ./yourapp.out   &

nohup命令:让你的app忽略SIGHUP信号

&   :让程序去后台执行,即不再占用当前终端了

(5)  进程间通知

技巧: 通过共享内存的方式, 接受者进程把自己的pid写入共享内存,发送者进程获取在合适的时机发送信号即可

实现代码:

#include <stdio.h>

#include <signal.h>

/*signum:表示当前发生了那个信号*/

void sigint_handle(int signum)

{

    printf("process recv sigINT\n");

}


void sigsegv_handle(int signum)

{

    printf("process recv sigsegv\n");

    //exit(0);

}


int main()
{
signal(SIGINT,sigint_handle ); //告诉进程,以后收到SIGINT信号,就去执行后面的方法

signal(SIGSEGV, sigsegv_handle);


while(1){

    int *pint = (int*)0x12345678;

    *pint = 1234;


    printf("I am sleeping\n");

    sleep(1);

}

    return 0;

}

4.网络通信(socket)

二、线程(thread)

        一个进程内部其实 也可以创建多个 线程,各个线程独立运行(不需要拷贝fork),

进程内部的线程,共享了进程的资源. (不需要进程间通信)

1.线程的创建

创建一个线程(执行单元, 执行体 应该也是 一个函数)

tid: thread id,每个线程的有一个编号 int

#include <pthread.h>

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

pthread_t:线程的标示

attr:线程的属性设置   NULL

thread_funx: 线程执行体所要 执行的函数,类比main

arg:执行上述函数,要传递的参数

返回值:

        0-success

        其他 返回的是 errno

2.线程的结束

(1)线程退出 循环

(2)线程主动调用pthread_exit退出

上述两种方式,不管哪种方式  都会残留一点尸体在系统中,同时向主线程传递了一个指针

线程的退出:

        #include <pthread.h>

        void pthread_exit(void *retval);

该函数会释放线程资源,并向主线程main传递消息,最后留下尸体在系统中

等待主线程main 来收尸

(3)主线程给其他线程收尸  

#include <pthread.h>

int pthread_join(pthread_t tid, void **retval);

tid:只等待该线程

retval:  获取该线程tid,的返回指针

阻塞等待(死等)#某个#子线程退出,如果发现某个子线程退出,则该函数返回,并得到该线程传递的指针

返回值:

        0-success , 否则 errno

#include <stdio.h>

#include <string.h>

#include <unistd.h>

#include <pthread.h>


/*代码编译 gcc thread.c -o thread.out -lpthread*/

struct pthead_private_data {

        int exitcode;

        int temp;

        int humidity;

        char wind;

        char des[32];

};

int aaaaaa = 1;

int thread_exit_flags = 0; //0-线程不退出 1-线程退出

void *thread_funx_handle (void *arg)

{

        int dd;

        struct pthead_private_data  *p=arg; //全局变量


while(1){

    if(thread_exit_flags == 1){

        p->exitcode = 8;

        pthread_exit(p); //线程主动退出,释放资源,并向主线程传递指针p

                            }

    printf("in thread temp=%d wind=%d des:%s aa=%d\n",p->temp,p->wind,p->des,aaaaaa);

    sleep(1);

    }

    printf("I am thread,i will exit\n");

}

    struct pthead_private_data pdata;

int main(int argc,char **argv )

{

    int ret;

    pdata.temp=1234; pdata.humidity=456;pdata.wind=46;strcpy(pdata.des,"cloudy");

    pthread_t tid; /*每个线程 都有一个编号, 称为线程id,简称 thread id :tid  */
    
    ret = pthread_create( &tid, NULL, thread_funx_handle, &pdata  );

    if(ret){

        printf("pthread create err %s\n",strerror(ret));

            return -34;

    }

    while(1){

        aaaaaa++;

        if(aaaaaa==5){

            thread_exit_flags = 1;

            printf("end thread\n");

            struct pthead_private_data *pdata;   //存放子线程的返回值指针

            ret = pthread_join(tid,  (void**)&pdata);

            if(ret){

                printf("pthread_join err %s\n",strerror(ret));

                    return -34;

                }

    printf("main thread find subthread exit code=%d\n",pdata->exitcode);

        }

    printf("I am main \n");
    
    sleep(1);

    }


    return 0;

}

(4)Thread_death

主线程结束和子线程的关系 detach ,那么子线程就找不到主线程了,当子线程结束的时候,会自动释放所有资源。

        pthread_detach( pthread_t tid);

#include <stdio.h>

#include <pthread.h>

struct private {

int temp;

long data;

char des[128];

};

int sub_thread_run = 1;  //子线程可以运行

void *thread_handle_fun(void *args)

{

struct private *p = args;


while( 1 ){

if(sub_thread_run == 0){

printf("I am sub thread,I will exit\n");

pthread_exit(NULL);

}

printf("in sub thread\n");

sleep(1);

}

return NULL;

}

struct private data;

int main(int argc,char **argv)

{



    pthread_t tid;

    int ret = pthread_create(&tid,NULL,thread_handle_fun,&data);

if(ret){

    printf("pthread_create err\n");

    return -34;

}


pthread_detach(tid);
    
    int aa = 1;

    while(1){

        if( (aa++) == 5){

            sub_thread_run = 0;// 让子线程结束

                    }


    printf("I am main\n");

    sleep(1);
    
        }

    return 0;

}
  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

春风从不入睡、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值