您的位置:首页 > 其它

进程线程复习笔记

2017-01-24 13:54 316 查看
程序-->预处理-->编译--->汇编--->链接

.c # .S .o .exe - ./a.out

1. 进程是系统资源分配的最小单位 cpu 内存 堆栈 bss

2. 调度,

3. 进程、程序区别

4. RTOS real time OS vxworks linux

  时间片轮转 单核 4核 、

  内核抢占 处理紧急任务,

  微观上:串行

  宏观 : 并行

 进程在内存里布局:

 库?

 局部变量、形参 栈

 全局 变量、静态变量【全局、局部】 都是一直存在的 data bss

 内核维护进程的必须的一个结构体

 schedule()

内核代码:

struct task_struct {

volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */

进程状态: 运行态、停止态、死亡、阻塞

void *stack;

atomic_t usage;

unsigned int flags; /* per process flags, defined below */

unsigned int ptrace;

 

#ifdef CONFIG_SMP

struct llist_node wake_entry;

int on_cpu;

struct task_struct *last_wakee;

unsigned long wakee_flips;

unsigned long wakee_flip_decay_ts;

 

int wake_cpu;

#endif

int on_rq;

 

int prio, static_prio, normal_prio;

unsigned int rt_priority; 优先级

const struct sched_class *sched_class;

struct sched_entity se;

struct sched_rt_entity rt;

#ifdef CONFIG_CGROUP_SCHED

struct task_group *sched_task_group;

#endif

struct sched_dl_entity dl;

 

#ifdef CONFIG_PREEMPT_NOTIFIERS

/* list of struct preempt_notifier: */

struct hlist_head preempt_notifiers;

#endif

 

#ifdef CONFIG_BLK_DEV_IO_TRACE

unsigned int btrace_seq;

#endif

 

unsigned int policy;

int nr_cpus_allowed;

cpumask_t cpus_allowed;

 

#ifdef CONFIG_PREEMPT_RCU

int rcu_read_lock_nesting;

char rcu_read_unlock_special;

struct list_head rcu_node_entry;

#endif /* #ifdef CONFIG_PREEMPT_RCU */

#ifdef CONFIG_TREE_PREEMPT_RCU

struct rcu_node *rcu_blocked_node;

#endif /* #ifdef CONFIG_TREE_PREEMPT_RCU */

#ifdef CONFIG_RCU_BOOST

struct rt_mutex *rcu_boost_mutex;

#endif /* #ifdef CONFIG_RCU_BOOST */

 

#if defined(CONFIG_SCHEDSTATS) || defined(CONFIG_TASK_DELAY_ACCT)

struct sched_info sched_info;

#endif

 

struct list_head tasks;

#ifdef CONFIG_SMP

struct plist_node pushable_tasks;

struct rb_node pushable_dl_tasks;

#endif

 

struct mm_struct *mm, *active_mm;进程内存使用情况

【虚拟内存】mmu

#ifdef CONFIG_COMPAT_BRK

unsigned brk_randomized:1;

#endif

#if defined(SPLIT_RSS_COUNTING)

struct task_rss_stat rss_stat;

#endif

/* task state */

int exit_state;

int exit_code, exit_signal;

int pdeath_signal; /* The signal sent when the parent dies */

unsigned int jobctl; /* JOBCTL_*, siglock protected */

 

/* Used for emulating ABI behavior of previous Linux versions */

unsigned int personality;

 

unsigned in_execve:1; /* Tell the LSMs that the process is doing an

* execve */

unsigned in_iowait:1;

 

/* task may not gain privileges */

unsigned no_new_privs:1;

 

/* Revert to default priority/policy when forking */

unsigned sched_reset_on_fork:1;

unsigned sched_contributes_to_load:1;

 

pid_t pid; 进程号 唯一标识一个进程

pid_t tgid; 线程组id号

 

#ifdef CONFIG_CC_STACKPROTECTOR

/* Canary value for the -fstack-protector gcc feature */

unsigned long stack_canary;

#endif

/*

* pointers to (original) parent process, youngest child, younger sibling,

* older sibling, respectively. (p->father can be replaced with

* p->real_parent->pid)

*/

struct task_struct __rcu *real_parent; /* real parent process */

struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */

/*

* children/sibling forms the list of my natural children

*/

struct list_head children; /* list of my children */

struct list_head sibling; /* linkage in my parent's children list */

struct task_struct *group_leader; /* threadgroup leader */

主线程进程描述符、linux下没有专门实现线程的结构体,用进程代替线程,做一些特殊处理

/*

* ptraced is the list of tasks this task is using ptrace on.

* This includes both natural children and PTRACE_ATTACH targets.

* p->ptrace_entry is p's link on the p->parent->ptraced list.

*/

struct list_head ptraced;

struct list_head ptrace_entry;

 

/* PID/PID hash table linkage. */

struct pid_link pids[PIDTYPE_MAX];

struct list_head thread_group;

struct list_head thread_node;

 

struct completion *vfork_done; /* for vfork() */

int __user *set_child_tid; /* CLONE_CHILD_SETTID */

int __user *clear_child_tid; /* CLONE_CHILD_CLEARTID */

 

cputime_t utime, stime, utimescaled, stimescaled;

cputime_t gtime;

#ifndef CONFIG_VIRT_CPU_ACCOUNTING_NATIVE

struct cputime prev_cputime;

#endif

#ifdef CONFIG_VIRT_CPU_ACCOUNTING_GEN

seqlock_t vtime_seqlock;

unsigned long long vtime_snap;

enum {

VTIME_SLEEPING = 0,

VTIME_USER,

VTIME_SYS,

} vtime_snap_whence;

#endif

unsigned long nvcsw, nivcsw; /* context switch counts */

struct timespec start_time; /* monotonic time */

struct timespec real_start_time; /* boot based time */

/* mm fault and swap info: this can arguably be seen as either mm-specific or thread-specific */

unsigned long min_flt, maj_flt;

 

struct task_cputime cputime_expires;

struct list_head cpu_timers[3];

 

/* process credentials */

const struct cred __rcu *real_cred; /* objective and real subjective task

* credentials (COW) */

const struct cred __rcu *cred; /* effective (overridable) subjective task

* credentials (COW) */

char comm[TASK_COMM_LEN]; /* executable name excluding path

- access with [gs]et_task_comm (which lock

it with task_lock())

- initialized normally by setup_new_exec */

/* file system info */

int link_count, total_link_count;

#ifdef CONFIG_SYSVIPC

/* ipc stuff */

struct sysv_sem sysvsem;

#endif

#ifdef CONFIG_DETECT_HUNG_TASK

/* hung task detection */

unsigned long last_switch_count;

#endif

/* CPU-specific state of this task */

struct thread_struct thread;

/* filesystem information */

struct fs_struct *fs; 当前进程工作目录和根目录 pwd

/* open file information */

struct files_struct *files;打开的文件相关信息结构体

/* namespaces */

struct nsproxy *nsproxy;

};

进程的四种状态:

可运行态:

当进程正在被CPU执行,或已经准备就绪随时可由调度程序执行,则称该进程为处于运行状态(running)。进程可以在内核态运行,也可以在用户态运行。当系统资源已经可用时,进程就被唤醒而进入准备运行状态,该状态称为就绪态。

此时不一定拥有cpu

浅度睡眠态(可中断):

进程正在睡眠(被阻塞),等待资源到来时唤醒,也可以通过其他进程信号或时钟中断唤醒,进入运行队列。

深度睡眠态(不可中断):

其和浅度睡眠基本类似,但有一点就是不可由其他进程信号或时钟中断唤醒。只有被使用wake_up()函数明确唤醒时才能转换到可运行的就绪状态。

暂停状态:

当进程收到信号SIGSTOP【ctrl+z】、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。

kill -l/SIGSTOP:强制进入暂停状态,

SIGSTRACED gdb 

僵死状态:

当进程已停止运行,但其父进程还没有询问其状态时,未释放PCB,则称该进程处于僵死状态。

子进程回收的3种方法:

1:父进程 wait waipid?SIGCHLD

2 : 父进程在创建子进程,可以通知内核不想回收该子进程,

3:父进程没有回收,由init成为该子进程的继父进程,子进程退出的时候回收他

用户态下只能访问用户空间

 

内核模式的代码可以无限制地访问所有处理器指令集以及全部内存和I/O空间。

用户模式的进程要享有此特权,它必须通过系统调用向设备驱动程序或其他内核模式的代码发出请求。

 

用户模式可以缺页,内核不允许

虚拟内存是按页管理的,每一个虚拟内存的页都要有物理地址页对应

缺页:某些虚拟地址页,可以没有物理地址对应

 

通过int 0x80方式触发中断,进入内核态中断处理。【Intel芯片的汇编指令】

 

用户空间的应用程序,通过系统调用,进入内核空间。这个时候用户空间的进程要传递很多变量、参数的值给内核,内核态运行的时候也要保存用户进程的一些寄存器值、变量等。

 

所谓的“进程上下文”,可以看作是用户进程传递给内核的这些参数以及内核要保存的那一整套的变量和寄存器值和当时的环境等。

 

硬件通过触发信号,导致内核调用中断处理程序,进入内核空间。这个过程中,硬件的一些变量和参数也要传递给内核,内核通过这些参数进行中断处理。所谓的“中断上下文”,其实也可以看作就是硬件传递过来的这些参数和内核需要保存的一些其他环境(主要是当前被打断执行的进程环境)。

 

进程的启动:

1 手动启动

前台 ./a.out

后台 &

top 

ps -ef | grep a.out

2 命令

at 1:49pm today

ls > ~/ttt.log

ctrl + d退出 

cron

*/1 * * * * date >> ~/t.log 每分钟将时间打印到文件中

top

ps

kill 发送信号 kill -9 pidnum

bg fg jobs

jobs:查看当前有多少在后台运行的命令

&:运行程序放到后台

ctrl-z 将一个正在前台运行的进程放到后台,并且暂停

fg 将后台运行命令调度到前台

bg 将一个在后台暂停额命令,变成继续执行,如果有多个 bg N 序号

 

proc 目录【参考实验手册】

fork

拷贝当前进程,生成一个副本

内核:创建一个新的task_struct【打开文件描述符表】?,子进程集成父进程数据空间、堆栈等资源,这些资源不是共享,是一个新的副本 

file没有拷贝

 

因为子进程拷贝了父进程的堆栈段,所以两个进程都停留在fork(),等待返回,fork()会返回两次,一次是父进程,一次是子进程

 

 

如何查看最大进程数

cat /proc/sys/kernel/pid_max

实例1 课件的例子

The return value is 11620 In parent process!! My PID is 11619, My PPID is 3188

The return value is 0 In child process!! My PID is 11620, My PPID is 1

value pid ppid

11620?11619?3188 父

0?11620 1 子

例2

#include <stdio.h>

#include <stdlib.h>

#include <unistd.h>

 

int glob = 6;

 

int main()

{

int local;

int pid;

 

local = 88;

printf("parent[pid = %d]: before fork, glob(&glob) = %d(%p), local(&local) = %d(%p)\n",

getpid(), glob, &glob, local, &local );

 

if((pid = fork()) < 0) {

perror("fail to fork");

return -1;

}

if(pid == 0) { /* child process */

printf("child[pid = %d]: after fork, glob(&glob) = %d(%p), local(&local) = %d(%p)\n",

getpid(), glob, &glob, local, &local );

 

glob++;

local++;

 

printf("child[pid = %d]: changed data after fork, glob(&glob) = %d(%p), local(&local) = %d(%p)\n",

getpid(), glob, &glob, local, &local );

}

else { /* parent process */

sleep(2);

printf("parent[pid = %d]: after fork, glob(&glob) = %d(%p), local(&local) = %d(%p)\n",

getpid(), glob, &glob, local, &local );

 

}

 

/* return euqal to exit(0), but exit may cause a compile warning

* due to main() is declared to return with an integter

*/

return 0;

}

 

parent[pid = 12270]: before fork, glob(&glob) = 6(0x804a024), local(&local) = 88(0xbfebee38)

child[pid = 12271]: after fork, glob(&glob) = 6(0x804a024), local(&local) = 88(0xbfebee38)

child[pid = 12271]: changed data after fork, glob(&glob) = 7(0x804a024), local(&local) = 89(0xbfebee38)

parent[pid = 12270]: after fork, glob(&glob) = 6(0x804a024), local(&local) = 88(0xbfebee38)

 

1.全局变量 互相不影响

子进程是父进程的拷贝,全局变量和局部变量都 有新的备份,【有的新的内存对应】

2. 全局变量 局部变量地址是一样的。

虚拟地址,对应的真正的物理地址不一样,mmu会自动帮我们将虚拟地址转换成物理地址,

物理地址的分配:os管理的

  

如果我fork之前打开了一个文件,fork之后,父子进程共享文件,

如果子进程修改了文件位置,父进程看到的这个值有没有发生变化?

综合练习:编写一个拷贝文件程序,父进程拷贝文件的上半部分,子进程拷贝文件的下半部分。

 

僵尸进程

父进程先退出,子进程后推出,子进程会被init继承

子进程先退出,父进程后退出,父进程没有回收子进程,子进程就变成僵尸进程

僵尸进程不可以kill,要想手动,只能kill父进程

exec函数族到底是干什么的?

原进程的data/text 堆栈都被替换了,pid留着

包含p字母:

PATH execvp和execlp

程序名作为参数,在PATH中搜索 其他的必须指明全路径

包含l字母:

List execl,execlp,execle

接受可变长参数

包含v字母:

vargs execv,execvp,execve

字符串数组作为调用程序的参数,以NULL结束数组 ls –ef NULL

包含e字母:

enviroment多了指明环境变量列表的参数

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[]);

 

execvp("ls",argv); // ./r -ef

execv("/bin/ls",argv); ./run -l

execl("/bin/ls","ls","-l","/",NULL);

指令路径 指令 参数 目录 空

execlp("ls","ls","-al","/",NULL);

system("ls -l");

strace

 

ps -axj

x 显示与终端无关的进程

进程的一些符号: 

所有的守护进程都是以超级用户启动 UID=0

没有终端控制 tty

终端进程组ID -1 tpgid

所有守护进程的父进程都是init PPID=1

进程组:

shell里的每个进程都属于一个进程组,方便向所有进程发送信号

PGID

每个进程组都有一个组长,

生命周期

最后一个进程终止或者其加入其它进程组

会话

一般一个用户登录后新建一个会话,登录的第一个进程叫?会话领头进程,通常是一个shell bash

pid=sid

一个会话一般会拥有一个控制终端用于执行IO操作,

一个会话只能由一个控制终端

前台进程组

该进程组中的进程能够向终端设备进程读写操作的进程组

我们发送的信号 数据只能给前台进程组

后台进程组

只能向终端写 不能读

终端进程组ID

用来标识一个进程是否处于一个和终端相关的进程组中

前台进程组的TPGID=PGID,

后台的 不相等,

总结

1. 每个会话有且只有一个前台进程组,但会有0个或者多个后台进程组

2. 产生在控制终端上的输入和信号将发送给会话的前台进程组的所有进程

对于输出来说,前台后台共享

3. 终端上的连接断开时,挂起信号将发送到控制进程【与控制终端建立连接的会话领头进程称为控制进程】

4. 一个用户登录后创建一个会话,

一个会话只存在一个前台进程组,

第一次登录后第一个创建的进程是shell,也就是会话领头进程,该领头进程缺省处于一个前台进程组中并且打开一个控制终端可以进行数据读写

 

当在shell里运行一行命令创建一个新的进程组,命令行中如果有多个命令会创建多个进程,这些进程都处于该新建进程组,shell就把他们设置成前台进程组并将自己暂时设置为后台进程组

 

由于守护进程是脱离控制终端的,因此,完成第一步后就会在shell终端里造成一程序已经运行完毕的假象。之后的所有后续工作都在子进程中完成,而用户在shell终端里则可以执行其他的命令,从而在形式上做到了与控制终端的脱离

 

由于父进程已经先于子进程退出,会造成子进程没有父进程,从而变成一个孤儿进程。在Linux中,每当系统发现一个孤儿进程,就会自动由1号进程收养。原先的子进程就会变成init进程的子进程。

setsid:

让进程摆脱原会话的控制; 让进程摆脱原进程组的控制;

让进程摆脱原控制终端的控制

 

由于调用fork函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然之后父进程退出了,但原先的会话期、进程组、控制终端等并没有改变,因此,还没有真正意义上独立开来,

由于使用fork新建的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带来了诸多的麻烦。

这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸载

 

从终端输入的字符不可能达到守护进程,守护进程中用常规的方法(如printf)输出的字符也不可能在终端上显示出来。所以,文件描述符为0、1和2的三个文件(对应标准输入、标准输出和标准错误这三个流)已经失去了存在的意义,也应被关闭。

 

线程

1.线程是允许同一个进程执行不同任务的机制。在用户看来是并行执行。

2.是操作系统调度的最小单元,接受操作系统的异步调度。

3.线程执行的代码称为线程函数,不同的线程执行的是同一程序的不同部分,或者是相同部分。

4.线程可由进程创建,操作系统在创建进程时会创建一个主线程,程序main()函数是主线程的入口。

ps -ef f

用树形显示进程和线程,比如说我想找到proftp现在有多少个进程/线程,可以用

$ ps -ef f | grep proftpd

pstree -p

内核中的结构与作用:

1. 一个标准线程由线程ID,当前指令指针pc,寄存器集合和堆栈组成

2. 线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,但是可以共享所属进程的所有资源

3. 一个线程可以创建和撤销另一个线程

4.线程也有就绪、阻塞、运行

5.每个程序至少有一个线程,如果只有一个线程就是程序本身

pthread_attr_X : 和线程属性相关的函数,可参考帮助文档

D_REENTRANT:

在一个多线程程序里,默认情况下,只有一个errno变量供所有的线程共享。在一个线程准备获取刚才的错误代码时,该变量很容易被另一个线程中的函数调用所改变。

pthread_setcancelstate / pthread_setcanceltype

 

线程就理解为一个执行者好了,它没有资源。进程概念上是资源的宿主。Linux上的理念是大家都是执行者,资源共享。

进程与线程相同点:

<1>?比如都具有ID,一组寄存器,状态,优先级以及所要遵循的调度策略。

<2>?每个进程都有一个进程控制块,线程也拥有一个线程控制块(在Linux内核,线程控制块与进程控制块用同一个结构体描述,即struct task_struct),这个控制块包含线程的一些属性信息,操作系统使用这些属性信息来描述线程。

<3>?线程和子进程的创建者可以在线程和子进程上实行某些控制,比如,创建者可以取消、挂起、继续和修改线程和子进程的优先级。

进程与线程不同点:

<1>?主要区别:每个进程都拥有自己的地址空间,但线程没有自己独立的地址空间,而是运行在一个进程里的所有线程共享该进程的整个虚拟地址空间。

<2>?线程的上下文切换时间开销比进程上下文切换时间开销要小的多

<3>?线程的创建开销远远小于进程的创建

<4>?子进程拥有父进程的地址空间和数据段的拷贝,因此当子进程修改它的变量和数据时,它不会影响父进程中的数据,但线程可以直接访问它进程中的数据段。

<5>?进程之间通讯必须使用进程间通讯机制,但线程可以与进程中的其他线程直接通讯

<6>?线程可以对同一进程中的其他线程实施大量控制,但进程只能对子进程实施控制

<7>?改变主线程的属性可能影响进程中其他的线程,但对父进程的修改不影响子进程

  

线程的pid的值是一样的,lwp不一样

线程的退出

1. 线程从其工作函数返回 return

2. 被同一个进程的其他线程取消 pthread_cancel

3. 调用pthread_exit

返回值或者传递数据,最好是堆上的,或者是全局变量

不可以是局部变量的地址

pthread_cancel 请求取消同一个进程的其他线程,并不等待线程终止,仅仅提出请求

示例:

用多线程,实现,一个线程 每隔1秒打印"a",连续打印3次,另外一个线程每隔1秒打印"b",连续打印2次,

1. 至少创建两个线程,

2. 一个线程打印a 一个线程打印b

int num;

while(1)

{

lock();

for(num=0;num<3;num++)

{

sleep(1);

printf("aaaaaa\n");

}

unlock();

}

 

int num;

while(1)

{

lock();

for(num=0;num<2;num++)

{

sleep(1);

printf("bbbbbb\n");

}

unlock();

}

 

信号量

 

sem_getvalue(&sem, &sval)

面试:

1. 什么是进程?

程序动态执行过程

资源分配最小单位 内存 cpu、

 

2.调度????

时间片轮转,内核抢占,

内核调度,

进程状态: 

3.内核如何管理?

schedule()

task_struct结构体

ps -ef -axj top

 

4. 进程在内存里的是如何管理? 

jobs

bg

fg

 

5. 进程如何创建?

手动 ./a.out

命令 at cron 可以执行程序 脚本

程序方式

system

fork+exec

6. 进程的退出

exit() _exit()

return

子进程退出

子进程在父进程之后退出,孤儿进程。会被init继承

子进程必须回收他,僵尸

7. 子进程回收?

1).wait waitpid

2)创建子进程,高速内核,

3)init继承,init回收

8. 什么是守护进程?

后台运行,终端无关,

有什么特点?

守护进程创建步骤

1. fork,父进程退出

2. setsid

3.文件路径

4.掩码umask();

5.关闭文件描述符

实现自己的功能

9. fork 拷贝了哪些资源??

task_struct【打开文件列表】 data 堆栈

 

10.exec 主要功能,

除了进程号,其他的全部被替换了 1.什么是线程?

允许执行多个任务的机制

并行执行

异步调度

执行程序的不同部分

 

11.如何调度?

12.内核如何管理?

线程共用pid,

lwp

线程组, 

ps -eLf pstree -p

 

13.那些资源是共享的,那些资源是私有的?

有独立的堆栈

 

14.线程如何创建

pthread_create()

 

15.线程的退出

return

pthread_exit

pthread_cancel 

 

16.回收

pthread_join

主线程退出了,进程就退出了,其他子线程也就不存在了

17.线程函数传参

只能传堆、全局变量、静态

思考:

多线程下载,原理???
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: