linux高级编程进程,Linux环境高级编程基础

Linux是最受程序员欢迎的操作系统之一。第一它是开源的,第二它的系统调用少,第三它的抽象更到位。一切皆进程,一切皆文件。这两个“一切”已经把Linux的基调表达的充分无疑。统一接口就是对用户最大的友善。我想没有一个程序员愿意学习动辄就上千个系统调用,还不知道是怎么实现的操作系统。

进程及线程出现的背景

如果想要深入理解一个事物的本质,最好的方式就是去追寻这个事物出现的历史背景和推动因素。

最早的计算机是没有操作系统的,只有输入、计算和输出功能,用户输入一个指令,计算机完成操作,大部分时间计算机都在等待用户输入指令,这样的处理性能显然是很低效的,因为人的输入速度是远远比不上计算机的运行速度的。

为了解决手工操作带来的低效,批处理操作系统应用而生。批处理简单来说就是把要执行的指令记录下来形成一个指令清单,然后交给计算机去处理。这可以很大提升性能。

批处理有一个明显的缺点:计算机一次只能执行一个任务,如果某个任务是IO操作,那么CPU就要等这个任务完成,才能执行下一个任务。但其实这个时候CPU是空闲的,会造成CPU资源浪费。

为了进一步提升性能,发明了“进程”,用进程来对应一个任务,每个任务都有自己独立的内存空间,进程间互不相关,由操作系统来进行调度。注意:此时的CPU并没有多核的概念,为了达到多进程并行运行的目的,采用了时间片机制。每个片段只能执行某个进程中的指令。 虽然从操作系统和CPU的角度来说还是串行处理的,但是由于CPU的处理速度很快,从用户的角度来看,感觉是多进程在并行处理。

多进程虽然要求每个任务都有独立的内存空间,进程间互不相关,但从用户的角度来看,两个任务之间能够在运行过程中就进行通信,会让任务设计变得更加灵活高效。否则如果两个任务设计过程中不能通信,只能是A任务将结果写到存储,B任务再从存储读取进行处理,不仅效率低,而且设计更加复杂。为了解决这个问题,进程间的通信被设计出来,包括管道、消息队列、信号量、共享内存、socket等。

多进程让多任务能够并行处理,但本身还有缺点,单个进程内部只能串行处理,而实际上很多进程内部的子任务并不要求严格按照时间顺序来执行,也需要并行处理。怎么办呢?当然断续拆解和细分任务是一个解决方案,但是细化总会有个度,如果任务之间的通信成本比较创建任务本身还高,就没有必要细化下去了, 但有并行处理的需求。线程就闪亮登场了。线程是进程内部的子任务,都共享进程同一份进程数据。

为了保证数据的正确性,又发明了互斥锁和同步机制。

有了线程,操作系统调度的最小单元就变成了线程,而进程变成了操作系统分配资源的最小单元。有了多核CPU,任务执行也不再是分时系统,多个线程可以同时在多个CPU上运行,此时才做到时间上的真正并行,目前操作系统处理多核CPU的方案最主要是SMP(Symmetric Multi-Processor对称多处理器结构)。

linux进程

1. 进程的创建

linux使用fork创建一个进程。linux系统运行起来后会创建一个为编号为1的init的进程。后面的进程都是init进程的后代。

#include

#include

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

int pid = fork();

printf("pid = %d\n",pid);

if (pid == 0) {

printf("pid = %d,my parent pid = %d\n",getpid(),getppid());

} else {

printf("pid = %d,my parent pid = %d\n",getpid(),getppid());

wait(NULL);

}

return 0;

}

这个函数为返回两次,如果0返回给子进程,如果大于0返回给父进程子进程的pid。

2. 进程运行状态机

6ffe406542d0

image.png

运行:当一个进程在处理机上运行时,则称该进程处于运行状态。处于此状态的进程的数目小于等于处理器的数目,对于单处理机系统,处于运行状态的进程只有一个。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。

就绪:当一个进程获得了除处理机以外的一切所需资源,一旦得到处理机即可运行,则称此进程处于就绪状态。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。

阻塞:也称为等待或睡眠状态,一个进程正在等待某一事件发生(例如请求I/O而等待I/O完成等)而暂时停止运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。

3.进程回收

回收原则:谁创建谁回收,回收不了init进程负责。

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

4.进程在内核本质

进程在内核中的形态就是task_struct,主要数据如下:

struct task_struct{

#ifdef CONFIG_THREAD_INFO_IN_TASK

/*

* For reasons of header soup (see current_thread_info()), this

* must be the first element of task_struct.

*/

struct thread_info thread_info;

#endif

/* -1 unrunnable不可运行, 0 runnable可运行, >0 stopped已经停止: */

/* 进程状态TASK_RUNNING、TASK_INTERRUPTIBLE、TASK_UNINTERRUPTIBLE、TASK_STOPPED、TASK_TRACED */

volatile long state;

/* 进程标识符,相当于每一个学生的学号一样,标识符唯一标识进程 */

pid_t pid;

/* P所在进程组的领头进程的PID */

pid_t tgid;

/* Real parent process: */

/* 指向创建了P的进程的描述符,如果P的父进程不再存在,就指向进程1(init)的描述符(因此,如果用户运行一个后台进程而且退出了shell,后台进程就会成为init的子进程 */

struct task_struct __rcu *real_parent;

/* Recipient of SIGCHLD, wait4() reports: */

/* 指向P的当前父进程(这种进程的子进程终止时,必须向父进程发信号)。它的值通常与real_parent一致,但偶尔也可以不同,例如,当另一个进程发出监控P的ptrace()系统调用请求 */

struct task_struct __rcu *parent;

/*

* Children/sibling form the list of natural children:

*/

/* 链表的头部,链表中的所有元素都是P创建的子进程 */

struct list_head children;

/* 指向兄弟进程链表中的下一个元素或前一个元素的指针,这些兄弟进程的父进程都是P */

struct list_head sibling;

/* P所在进程组的领头进程的描述符指 */

struct task_struct *group_leader;

/* 用来表示进程与文件系统的联系,包括当前目录和根目录 Filesystem information: */

struct fs_struct *fs;

/* 表示进程当前打开的文件 Open file information: */

struct files_struct *files;

#ifdef CONFIG_NUMA

/* Protected by alloc_lock: */

struct mempolicy *mempolicy;

short il_prev;

short pref_node_fork;

#endif

}

5. exec家族

一段运行或是活的代码就是进程,其实这种说法是不精确的。更准确的说是系统先创建了进程,然后让进程去运行我们写得代码。只不过可执行文件在运行时系统先给我们创建了进程。可以使用exec模拟这一场景。

#include

#include

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

int pid = fork();

if (pid == 0) {

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

execv("./task",NULL);

} else {

wait(NULL);

}

return 0;

}

以上代码就是先创建一个进程,然后执行当前目录的task任务。

线程

使用pthread可以操作线程,对linux内核来说是不区分进程和线程的,它也是task_struct,只不过把内存、文件系统等资源设置成了共享的。当然进程比线程更重一些,更耗资源一些,其实linux的很多应用是使用进程实现,因为相对其它系统它本身就是轻量级的。这也是linux宣称的一切皆进程。

创建线程

#include

#include

#include

int a = 3;

void * thread_handle_fun(void * args) {

printf("thread_handle_fun a = %d\n",a);

printf("current thread = %d\n",pthread_self());

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

a = 4;

printf("thread_handle_fun modify a\n");

return NULL;

}

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

pthread_t pt;

if (pthread_create(&pt,NULL,thread_handle_fun,NULL) == -1) {

perror("create error!");

return -1;

}

printf("main current thread = %ul\n",pthread_self());

printf("main my pid = %d\n",getpid());

if (pthread_join(pt,NULL)) {

perror("thread is not exit");

return -1;

}

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

return 0;

}

注:当一个线程正常退出对宿主进程没有影响,但是如果一个线程异常退出了,宿主进程也会跟着挂。这也是安全要求较高的应用大多使用进程,而不是线程的原因。

#include

#include

#include

void * thread_handle_fun(void * args) {

printf("thread_handle_fun modify a\n");

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

sleep(1);

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

if (i ==3) {

pthread_exit(NULL);

//int *array = (int*) args;

//array[0] = 1;

}

}

return NULL;

}

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

pthread_t pt;

if (pthread_create(&pt,NULL,thread_handle_fun,NULL) == -1) {

perror("create error!");

return -1;

}

if (pthread_join(pt,NULL)) {

perror("thread is not exit");

return -1;

}

while(1) {

sleep(1);

}

return 0;

}

Linux文件

Linux 中所有内容都是以文件的形式保存和管理的,即一切皆文件,普通文件是文件,目录是文件,硬件设备(键盘、监视器、硬盘、打印机)是文件,就连套接字(socket)、网络通信等资源也都是文件。

Linux系统中,文件具体类型: 普通文件 、目录、字符设备和块设备(/dev)、套接字文件(socket)(/var/run/)、符号链接文件(symbolic link)、管道文件(pipe)。

6ffe406542d0

WeChat6cabeb442e842f871ee8b49a06eef941.png

1. 对文件的基本操作

libc操作代码

#include

#include

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

FILE * pfile = fopen("1.txt","w+");

char array[] = "this is a test of operating file";

char buffer[50];

fwrite(array,strlen(array) + 1,sizeof(char),pfile);

fseek(pfile,0,SEEK_SET);

fread(buffer,strlen(array) + 1,sizeof(char),pfile);

puts(buffer);

fclose(pfile);

return 0;

}

系统调用操作代码

#include

#include

#include

#include

#include

#include

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

int fd = open("1.txt",O_RDWR);

char str[] = "this is a test of operating file using system call.";

char buffer[50];

if (fd > 0) {

printf("sizeof(str) = %lu\n",sizeof(str));

printf("strlen(str) = %lu\n",strlen(str));

write(fd,str,sizeof(str));

lseek(fd,0,SEEK_SET);

read(fd,buffer,sizeof(str));

puts(buffer);

close(fd);

}

return 0;

}

一切皆文件kernel实现

一切皆文件的基本哲学是要对用户提供统一的操作界面或接口。内核的虚拟文件系统(VFS)提供此功能的支持。给用户空间的程序提供统一文件系统接口,给驱动程序提供统一的规约,允许不同的文件系统共存。

6ffe406542d0

WeChataa736f04effbf06bfffc88f19efb9c27.png

字符设置驱动

推荐阅读

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值