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<stdio.h>
#include<unistd.h>

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. 进程运行状态机

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<stdio.h>
#include<unistd.h>

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<stdio.h>
#include<stdlib.h>
#include<pthread.h>

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<stdio.h>
#include<stdlib.h>
#include<pthread.h>

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)。

WeChat6cabeb442e842f871ee8b49a06eef941.png

1. 对文件的基本操作
libc操作代码

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

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<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<unistd.h>

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)提供此功能的支持。给用户空间的程序提供统一文件系统接口,给驱动程序提供统一的规约,允许不同的文件系统共存。

https://blog.csdn.net/mignatian/article/details/81673713

WeChataa736f04effbf06bfffc88f19efb9c27.png

字符设置驱动

https://www.cnblogs.com/chen-farsight/p/6155518.html

推荐阅读

https://cloud.tencent.com/developer/article/1507511

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,026评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,655评论 1 296
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,726评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,204评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,558评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,731评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,944评论 2 314
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,698评论 0 203
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,438评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,633评论 2 247
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,125评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,444评论 3 255
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,137评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,103评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,888评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,772评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,669评论 2 271