进程和线程总结

一、进程基础

  1. 进程和程序

程序:是保存在磁盘上的实现了某个功能的代码模块的集合,是静态的,没有任何执行的概念。

进程:是程序的一次动态执行过程,进程是程序执行和资源管理额最小单位,进程是一个独立的可以调度的任务。

程序 = 代码+数据
进程 = 代码+数据+系统资源
进程 = task_struct+4G(虚拟)

2. 进程的标志

进程号:pid是唯一的标识进程的符号 getpid();

父进程号:ppid是标识当前进程父进程的符号 getppid();

注意:pid是进程的task_struct中的一项成员

task_strcut---》是PCB的一种,是Linux系统中描述进程的一个结构体

内容:

1 标识符——》pid/tid
2 状态
3优先级
4程序计数器(pc)
5 内存指针
。。。。

3. 进程类型

交互进程:给指令就有回应的进程如 ls ps

批处理进程:是一个进程集合,维护着一个进程的队列,负责按照时间先后顺序启动队列中的进程。

shut down -h time

守护进程:周期性的执行某项任务或等待某个事件发生的进程,不依赖shell终端,生存周期较长

(从开机开始运行到关机结束)

4. 进程相关的操作指令

ps -aux:查看系统中的进程

ps -axj:查看系统中的进程(有父进程)

top:实时查看linux系统进程

pstree:查看进程树

kill:关闭一个进程 kill -9 进程号

nice:以指定优先级运行进程 nice -20 ./test

renice:改变正在运行的进程的优先级 renice 19 进程号

5. 进程的状态

运行态:runing(R)

等待态:正在等待被运行, sleep

停止态:stop T/Z/X (kill -STOP pid)

Linux中进程的七种状态:

1 R运行状态(runing):并不意味着进程一定在运行中,也可以在运行队列中

2 S睡眠状态(sleeping):进程在等待事件完成(浅度睡眠,可以被唤醒)

3 D磁盘睡眠状态(disk sleep):不可中断睡眠(深度睡眠,不可以被唤醒,通常在磁盘写入时发生)

4 T停止状态(stopped):可以通过发送stop信号给进程来停止进程,可以发送SIGCONT信号让进程继续运行。

5 X死亡状态(dead):该状态为返回状态,在任务列表中看不到

6 Z僵尸状态(zombie):子进程退出,父进程还在运行,但是父进程没有读到子进程的退出状态值,子进程进入僵尸状态。

7 t追踪停止状态(trancing stop)

6. 进程的启动方式

两种:手动启动:./test

调度启动:在相应的配置文件中自动启动(/etc/init.d/rcS---->/bin/a.out

at:在指定的时间执行一个任务

at12:00 shutdown -S -T30

二、进程相关的系统调用函数

fork exec函数族 exit/_exit wait/waitpid

1 fork——》创建方式:复制了父进程的所有内容(代码段+数据段+系统资源段) pid_t fork(void)

作用:创建一个子进程

返回值:成功,同时返回两个值(pid>0和0)

= 0:子进程

>0 (子进程的pid):父进程

失败 :-1

孤儿进程:父进程先于子进程退出,子进程有init进程收养,此时的子进程就是孤儿进程,用pstree查看。孤儿进程没有危害。

僵尸进程:子进程先于父进程退出,父进程未处理子进程的退出状态,导致该子进程成为僵尸进程,僵尸进程占用一个task_struct,他会统一参与操作系统的调度,它本身没有空间,是无用的进程,我们应该避免它的产生。ps -aux

2 exec函数族——》替换进程(新的程序替换掉原有进程)

有6个函数,作用相同,传参方式不用

l:list 以列表方式传参,最后以NULL结束
v:vector以数组的方式传参,数组的最后一个元素为NULL
e:env环境变量,传参方式同数组
p:PATH,在PATH所指的路径中去找可执行程序

6个函数如下:

int execl(const char *path,const char *arg,....);
int execv(const char *path,char *const argv[]);
int execle(const char *path,const char *arg,...,char *const envp[]);
int execve(const char *path,char *const argv[],char *const envp[]);
int execlp(const char *file,const char *arg,...);
int execvp(const char *file,char *const argv[]);

3 exit/_exit

exit:退出当前进程并刷新IO缓存区

_exit:退出当前进程 不刷新IO缓存

4 wait/waitpid——》避免僵尸进程的产生

pid_t wait(int *status);——》阻塞函数

作用:等待任意子进程的退出状态

*status:指向子进程退出状态值的指针

返回值:成功:等到的子进程PID

失败:-1

pid_t waitpid(pid_t pid,int *status,int options);

作用:等待指定子进程的退出状态

pid:子进程的PID

*status:指向子进程退出状态值的指针

options:0:将waitpid函数变成了阻塞函数,若本次调用没有等到结果,则阻塞当前进程;若等到了结果,返回子进程的PID。

WNOHANG:将waitpid函数变成了非阻塞函数,若本次调用,没有等到子进程退出,waitpid返回0,如果等到了退出状态,waitpid返回,返回子进程的pid。

阻塞函数:该函数会获取一个结果,如本次调用没有获取到结果,该函数会阻塞当前进程,该函数不返回。
非阻塞函数:该函数会获取一个结果,如本次调用没有获取到结果,该函数不会阻塞当前进程,函数返回,返回一个非正确的值,之后用户可以通过多次调用该函数去获取正确的结果。

Day2

//PPID父进程、PID子进程、PGID组进程、SID会话进程、TTY终端
  1. 守护进程

定义:周期性执行某项任务或等待某个事件发生的进程,它的运行不依赖shell终端,它的生存周期较长, (从开机开始运行到关机才结束)

如何创建一个守护进程?

步骤1:创建一个子进程,父进程退出
pid = fork();if(pid > 0){exit(0)}
2 给子进程创建新的会话期,让它脱离当前shell终端
3 改变子进程的工作目录(改变当前目录为根目录/也可以不是根目录,要确保该目录不会被他人删除)chdir(“/”);
4 重设(取消)文件权限掩码
Umask(0);(取消文件权限掩码)
5 关闭所有文件描述符

2 线程相关

线程:是一种轻量级的进程,它统一参与和创建它的进程共享一块内存空间,线程和进程一样统一参与操作系统的调度。
引入线程的原因:进程间通信系统开销大,需要跨越地址空间;同一进程创建的两个线程间通信系统开销小不用跨越地址空间。

如何创建线程?

使用第三方线程库提供的相关函数pthread

Int pthread_create(pthread_t *thread,const pthread_attr_t *attr,

Void *(*start_routine)(void *),void *arg);

作用:创建一个线程

*thread:指向线程ID的指针,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,

*attr:指向线程属性的指针(通常给NULL)

参数3:指向线程执行函数的指针

*arg:传给线程执行函数的入参

返回值:0成功,非0失败

Int pthread_join(pthread_t thread,void **value_ptr)

作用:阻塞等待线程退出,回收线程退出状态值

参数1:线程ID

参数2:指向线程退出状态值的指针

返回值:0成功;非0失败

Int pthread_exit(void *value_ptr)

作用:退出当前线程

*value_ptr:指向线程退出状态的指针

返回值:0成功;非0失败

Int pthread_cancel(pthread_t thead)

作用:取消线程/关闭线程

Thead:线程ID

返回值:0成功;非0失败

Ubuntu(32) = linux内核+桌面管理器+工具集+应用软件+其他

3 多线程引入的问题

3.1 线程间通信

定义:线程间数据交换
如何实现线程间通信?
——》使用全局变量(在进程中定义)

3.2线程同步

同步:是指两个对象/事物/任务按照约定好的顺序相互配合完成一件事情

线程的同步:两个线程按照约定好的顺序相互配合完成一件事情

如何实现线程间同步?

使用无名信号量(二值信号量)

sem_t sema;信号量

sem_t semb;信号量

如何使用无名信号量?

使用P操作和V操作函数

P操作:sem_wait(&sema)

含义:sema 》 0:函数返回;sema--

Sema = 0:阻塞当前线程

V操作:sem_post(&semb)

含义:给监控的信号量+1 semb++

sem_wait()
Printf(“hello\r\n”);

Int flag = 0;

3.3线程互斥

引入:两个线程可以使用同一个线程执行函数,若在该线程执行函数中定义了一个公共的buf,两个线程同时使用公共的buf,就会出现资源的竞争访问问题,会造成存放数据的混乱问题。

什么时候加锁解锁?

在使用公共资源前加锁,使用完后解锁。

如何解决资源竞态?

加锁(互斥锁)pthread_mutex_t mutex;——》互斥锁;

Int pthread_mutex_init(pthread_mutex_t *mutex,

Pthread_mutexattr_t *attr)

作用:初始化互斥锁

*mutex:指向互斥锁对象的指针

*attr:互斥锁属性,一般给NULL

返回值:0成功;非0失败

Int pthread_mutex_lock(pthread_mutex_t *mutex)

作用:给公共资源加锁

*mutex:指向互斥锁对象的指针

返回值:0成功;非0失败

Int pthread_mutex_unlock(pthread_mutex_t *mutex)

作用:解锁

*mutex:指向互斥锁对象的指针

返回值:0成功;非0失败

Day3

进程间通信--》无名管道 有名管道 信号通信 共享内存 消息队列 信号灯 socket通信

通信领域概念:

单工通信:传输方向单向

半双工通信:传输方向是双向,但同一时刻只能一个方向

全双工通信:传输方向是双向,可以双向同时传送

1 无名管道--》内核文件--》无名

使用pipe创建无名管道

int pipe(int fd[2])

fd[0]:指向管道固定的读端

fd[1]:指向管道固定的写端

父进程创建无名管道,子进程继承无名管道,所以父子进程可操作同一个管道

总结:

1 只能用于具有亲缘关系的进程间通信

2 无名管道的通信方式通常当单工使用(本质上是半双工,但通信时刻不好把握)

3 无名管道文件只存在于内核中,在文件系统中不可见,管道中的通信数据只存在于内存中

2 有名管道--》内核文件--》有名

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

作用:用于创建有名管道

*filename:有名管道文件的名称

mode:文件权限

返回值:0 成功 -1 失败

总结:1 有名管道可以用于任意进程间的通信

2 可以实现双工通信(需要两个管道文件)

3 有名管道文件在文件系统中是可见的,但是数据大小为0,管道中的数据只存在于内存中

3 信号通信

信号通信:是在软件层面上对中断机制的一种模拟。是一种异步通信方式。

中断:是指在程序的执行过程中插入了另外一段程序的执行过程,该段程序执行结束后,CPU回到中断

点继续执行原来的程序。

Linux内核提供了64信号:kill -l

每一个信号都对应一个默认处理动作

关闭终端--》SIGHUP---》终止终端运行的进程

ctrl+c-->SIGINT--->终止当前进程

kill -9 1000--->SIGKILL--->终止进程1000

quit--->SIGQUIT-->中止进程

异步通信:通信中接收方并不知道信号(数据)什么时候会到达,当前进程一直准备着接收数据,同时也做

自己的事情,一旦信号(数据)到达,马上接收处理。所以是非阻塞通信模式。

同步通信:发送方发数据,接收方同步接收数据,双方需要在很短的时间内完成数据交换,否则会造成一方

阻塞。所以是阻塞通信方式。

在linux系统中,进程对信号的处理方式:

1 捕获信号

2 采用默认处理动作

3 忽略信号

int main()

{

alarm(2);

pause();-->挂起当前进程并等待信号,一旦SIGALRM信号来,会执行默认处理动作(终止当前进程)

printf("hello world\r\n");

return 0;

}

alarm(2)-->SIGALRM-->终止当前进程

alarm:是一个闹铃函数,它可以在进程中设置一个定时器,当定时时间到时,内核就向当前进程发送

SIGALRM信号。

pause:挂起当前进程,并等待一个信号的到来。

如果不想让系统终止当前进程,那么可以把信号对应的默认处理动作做修改(改成一个函数),那么如何

改变信号对应的默认处理动作呢?用signal

sighandler_t signal(int signum, sighandler_t handler);

作用:修改信号对应的默认处理动作

signum:信号

handler:信号对应的新的处理动作函数

实现:通过信号实现A进程和B进程的通信

条件:1 信号--》kill -l SIGUSR1 SIGUSR2

2 信号的发送方如何发信号?kill raise

kill:给对方进程发信号

raise:当前进程给自己发信号

int kill(pid_t pid, int sig)

作用:给指定的进程发信号

pid:进程号

sig:信号 SIGUSR1

返回值:0 成功 -1 失败

3 信号的接收方如何捕获接收信号?pause

A进程:发信号

1 得知对方进程的进程号 main函数传参

2 kill(pid,SIGUSR1);

./signal_killA 1000

B进程:收信号 并处理

1 输出进程号

2 signal(SIGUSR1,Func)

3 pause;

Day4——》进程间通信

1共享内存(IPCS查看)

原理:通信进程创建一块共享内存,然后找出一块本地内存和共享内存建议映射关系,之后往本地内存中存放数据(实际也将数据写入到了共享内存),被通信方进程找到共享内存,并和其建立映射关系,之后从本地内存中取数据,就是从共享内存中取数据

实现两个进程通信:

A 1:ftok(“.”,11)——》创建一个对方进程找到自己的标识符

2:shmget——》创建一片共享内存

3:shmat——》本地内存和共享内存映射

4:memset/memcpy 15个10

B: 1:key = ftok(“.”,11)

2:shmget

3:shmat——》本地内存和共享内存映射

4:printf(p);打印15个10

key_t ftok(const char*pathname,int proj_id)

作用:创建一个标识符

*pathname:指向文件名或带路径的文件名

proj_id:0~255

返回值:成功,是一个整形的标识符。失败是负一

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

作用:创建一片共享内存

Key:ftok的返回值

Size:共享内存的大小

Shmflg:共享内存属性&八进制的权限位

返回值:成功,标识共享内存的标识符,失败-1.

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

作用:从用户空间找一块本地内存和共享内存建立映射

Shmid:表示共享内存的标识符

*shmaddr:指向本地内存的指针,一般给NULL(空)

Shmflg:SHM_RDONLY:共享内存只读

默认0:共箱内存可读写

返回值:成功,指向映射成功的本地内存地址的指针

Int shmdt(const void *shmaddr);

作用:取消映射

*shmaddr:指向本地内存的指针

返回值:成功0,失败-1

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

作用:设置/获取共享内存属性

Shmid:共享内存的标识符

Cmd:IPC_STAT(获取对象属性)——》第三参数存放获取到的对象属性

IPC_SET(设置对象属性)——》第三参数用于存放要设置的对象属性

IPC_RMID(删除对象)——》第三个参数为NULL

返回值:成功0,失败-1

Ipcs:用来查看所有IPC对象

注意:共享内存中的数据会一直存在,除非删除共享内存

注意:调用自定义函数/库函数——》用户空间

调用系统调用函数——》用户空间——》内核空间——》用户空间

2消息队列

原理:通信方进程创建一个消息队列,然后往消息队列中存放一条或多条消息,被通信方进程找到该消息队列,然后从里边取消息,取一条消息后,队列长度自动减一。

消息的定义格式:

Struct 消息结构体名

{

消息类型;long m_type;

消息正文;char m_buf[N];

}

A: 1 ftok

2 msgget——》创建一个消息队列

3 msgsnd——》往消息队列放消息

B: 1 ftok

2 msgget——》获取消息队列ID

3 msgrcv——》从消息队列中读取一条消息(按照存放顺序或消息类型)

Int msgget(key_t key, int msgflg)

作用:创建/获取消息队列ID

Key:ftok的返回值

Msgflg:消息队列属性&八进制的权限位 IPC_CREAT

返回值:成功返回消息队列ID,失败-1。

Int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg)

作用:往消息队列中存放消息

Msqid:消息队列ID

*msgp:指向消息对象

Msgsz:消息大小

Msgflg:IPC_NOWAIT 消息没有发送完成函数也会立即返回。

0:直到发送完成函数才返回

返回值:成功0,失败 -1

Ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg);

作用:从消息队列中获取一条消息

Msqid:消息队列ID

*msgp:接受消息的结构体指针

Msgsz:接受消息的大小(消息正文大小)

Msgtyp:0:接收消息队列中第一个消息。

大于0:接受消息队列中第一个类型为msgtyp的消息

小于0:接受消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息。

Msgflg:0:若无消息函数会一直阻塞

IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG。

返回值:成功接受到消息的长度失败-1

Int msgctl(int msgqid,int cmd,struct msqid_ds *buf)

作用:设置或获取消息队列属性

Msqid:消息队列ID

Cmd:IPC_STAT(获取对象属性)——》第三参数存放获取到的对象属性

IPC_SET(设置对象属性)——》第三参数用于存放要设置的对象属性

IPC_RMID(删除对象)——》第三个参数为NULL

返回值:成功0,失败-1

注意:消息队列中的消息,取一条少一条

3信号灯 = 信号量

Linux内核给用户提供了三种信号灯:

1 无名信号灯——》两个线程的同步 sem_t sema;P/V操作函数

2 有名信号灯——》两个进程的同步

3 信号灯集

进程间同步:

A:1 创建有名信号灯 sem_open

sem_open

2 while(1)

{

sem_wait();

Hello;

Sleep(1)

sem_post();

}

B:

sem_t *sem_open(const char *name,int oflag,

mode_t mode,unsigned int value);

作用:创建一个有名信号灯

*name:信号灯的名称

Oflag:O_CREAT

Mode:操作信号的权限 0666

Value:信号灯的初值0/1(0阻塞,1执行)

返回值:成功,指向操作信号灯的指针

失败SEM_FAILED

注意:有名信号量存放在/dev/shm里,所以创建有名信号量时不需要指定信号量的存放路径。

Day5

信号灯集

指一个或者多个信号灯的集合,其中的每一个都是单独的计数信号灯,每个数字代表资源的数量。每一个信号灯可以实现一组进程的同步或互斥。

Int semget(key_t key,int nsems,int semflg);

作用:创建一个信号灯集

参数:

key:ftok的返回值

Nsems:信号灯的个数

Semflg:信号灯集的访问权限,通常为IPC_CREAT | 0666

返回值:成功,信号灯集的ID,失败-1

Int semop(int semid,struct sembuf *opsptr,size_t nops);

作用:操作信号灯

参数:

Semid:信号灯集ID

Struct sembuf

{

Short sem_num; //要操作的信号灯编号

Short sem_op; //0:等待,直到信号灯的值变成0

//1:释放资源,V操作

//-1:分配资源,P操作

Short sem_flg; //0,IPC_NOWAIT, SEM_UNDO

};

Nops:要操作信号灯的个数

返回值:成功:0,失败-1

Int semctl(int semid,int semnum,int cmd .../*union semunarg*/);

作用:设置或获取信号灯的属性

参数:

Semid:信号灯集ID

semnum:要修改的信号灯编号

Cmd:GETVAL:获取信号灯的值

SETVSL:设置信号灯的值

IPC_RMID:从系统中删除信号灯集合

返回值:0成功,-1失败

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值